gt status: Show runtime state for all agents (gt-zbmg8)
- Add AgentRuntime struct to track tmux session status - Implement discoverGlobalAgents() for Mayor/Deacon - Implement discoverRigAgents() for witness/refinery/crew/polecats - Update text output to show ✓ running / ✗ stopped for each agent - Color-code status: green for running, red for stopped - Include hook info inline with agent status - JSON output includes full runtime state in 'agents' field 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ import (
|
|||||||
"github.com/steveyegge/gastown/internal/git"
|
"github.com/steveyegge/gastown/internal/git"
|
||||||
"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/workspace"
|
"github.com/steveyegge/gastown/internal/workspace"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -37,22 +38,35 @@ func init() {
|
|||||||
|
|
||||||
// TownStatus represents the overall status of the workspace.
|
// TownStatus represents the overall status of the workspace.
|
||||||
type TownStatus struct {
|
type TownStatus struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Location string `json:"location"`
|
Location string `json:"location"`
|
||||||
Rigs []RigStatus `json:"rigs"`
|
Agents []AgentRuntime `json:"agents"` // Global agents (Mayor, Deacon)
|
||||||
Summary StatusSum `json:"summary"`
|
Rigs []RigStatus `json:"rigs"`
|
||||||
|
Summary StatusSum `json:"summary"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AgentRuntime represents the runtime state of an agent.
|
||||||
|
type AgentRuntime struct {
|
||||||
|
Name string `json:"name"` // Display name (e.g., "mayor", "witness")
|
||||||
|
Address string `json:"address"` // Full address (e.g., "gastown/witness")
|
||||||
|
Session string `json:"session"` // tmux session name
|
||||||
|
Role string `json:"role"` // Role type
|
||||||
|
Running bool `json:"running"` // Is tmux session running?
|
||||||
|
HasWork bool `json:"has_work"` // Has pinned work?
|
||||||
|
WorkTitle string `json:"work_title,omitempty"` // Title of pinned work
|
||||||
}
|
}
|
||||||
|
|
||||||
// RigStatus represents status of a single rig.
|
// RigStatus represents status of a single rig.
|
||||||
type RigStatus struct {
|
type RigStatus struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Polecats []string `json:"polecats"`
|
Polecats []string `json:"polecats"`
|
||||||
PolecatCount int `json:"polecat_count"`
|
PolecatCount int `json:"polecat_count"`
|
||||||
Crews []string `json:"crews"`
|
Crews []string `json:"crews"`
|
||||||
CrewCount int `json:"crew_count"`
|
CrewCount int `json:"crew_count"`
|
||||||
HasWitness bool `json:"has_witness"`
|
HasWitness bool `json:"has_witness"`
|
||||||
HasRefinery bool `json:"has_refinery"`
|
HasRefinery bool `json:"has_refinery"`
|
||||||
Hooks []AgentHookInfo `json:"hooks,omitempty"`
|
Hooks []AgentHookInfo `json:"hooks,omitempty"`
|
||||||
|
Agents []AgentRuntime `json:"agents,omitempty"` // Runtime state of all agents in rig
|
||||||
}
|
}
|
||||||
|
|
||||||
// AgentHookInfo represents an agent's hook (pinned work) status.
|
// AgentHookInfo represents an agent's hook (pinned work) status.
|
||||||
@@ -101,6 +115,9 @@ func runStatus(cmd *cobra.Command, args []string) error {
|
|||||||
g := git.NewGit(townRoot)
|
g := git.NewGit(townRoot)
|
||||||
mgr := rig.NewManager(townRoot, rigsConfig, g)
|
mgr := rig.NewManager(townRoot, rigsConfig, g)
|
||||||
|
|
||||||
|
// Create tmux instance for runtime checks
|
||||||
|
t := tmux.NewTmux()
|
||||||
|
|
||||||
// Discover rigs
|
// Discover rigs
|
||||||
rigs, err := mgr.DiscoverRigs()
|
rigs, err := mgr.DiscoverRigs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -111,6 +128,7 @@ func runStatus(cmd *cobra.Command, args []string) error {
|
|||||||
status := TownStatus{
|
status := TownStatus{
|
||||||
Name: townConfig.Name,
|
Name: townConfig.Name,
|
||||||
Location: townRoot,
|
Location: townRoot,
|
||||||
|
Agents: discoverGlobalAgents(t),
|
||||||
Rigs: make([]RigStatus, 0, len(rigs)),
|
Rigs: make([]RigStatus, 0, len(rigs)),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,6 +159,9 @@ func runStatus(cmd *cobra.Command, args []string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Discover runtime state for all agents in this rig
|
||||||
|
rs.Agents = discoverRigAgents(t, r, rs.Crews)
|
||||||
|
|
||||||
status.Rigs = append(status.Rigs, rs)
|
status.Rigs = append(status.Rigs, rs)
|
||||||
|
|
||||||
// Update summary
|
// Update summary
|
||||||
@@ -173,65 +194,60 @@ func outputStatusText(status TownStatus) error {
|
|||||||
fmt.Printf("%s %s\n", style.Bold.Render("⚙️ Gas Town:"), status.Name)
|
fmt.Printf("%s %s\n", style.Bold.Render("⚙️ Gas Town:"), status.Name)
|
||||||
fmt.Printf(" Location: %s\n\n", style.Dim.Render(status.Location))
|
fmt.Printf(" Location: %s\n\n", style.Dim.Render(status.Location))
|
||||||
|
|
||||||
// Summary
|
// Global Agents (Mayor, Deacon)
|
||||||
fmt.Printf("%s\n", style.Bold.Render("Summary"))
|
fmt.Printf("%s\n", style.Bold.Render("Agents"))
|
||||||
fmt.Printf(" Rigs: %d\n", status.Summary.RigCount)
|
for _, agent := range status.Agents {
|
||||||
fmt.Printf(" Polecats: %d\n", status.Summary.PolecatCount)
|
statusStr := style.Success.Render("✓ running")
|
||||||
fmt.Printf(" Crews: %d\n", status.Summary.CrewCount)
|
if !agent.Running {
|
||||||
fmt.Printf(" Witnesses: %d\n", status.Summary.WitnessCount)
|
statusStr = style.Error.Render("✗ stopped")
|
||||||
fmt.Printf(" Refineries: %d\n", status.Summary.RefineryCount)
|
}
|
||||||
fmt.Printf(" Active Hooks: %d\n", status.Summary.ActiveHooks)
|
fmt.Printf(" %-14s %s\n", agent.Name, statusStr)
|
||||||
|
}
|
||||||
|
|
||||||
if len(status.Rigs) == 0 {
|
if len(status.Rigs) == 0 {
|
||||||
fmt.Printf("\n%s\n", style.Dim.Render("No rigs registered. Use 'gt rig add' to add one."))
|
fmt.Printf("\n%s\n", style.Dim.Render("No rigs registered. Use 'gt rig add' to add one."))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rigs detail
|
// Rigs detail with runtime state
|
||||||
fmt.Printf("\n%s\n", style.Bold.Render("Rigs"))
|
fmt.Printf("\n%s\n", style.Bold.Render("Rigs"))
|
||||||
for _, r := range status.Rigs {
|
for _, r := range status.Rigs {
|
||||||
// Rig name with indicators
|
fmt.Printf(" %s\n", style.Bold.Render(r.Name))
|
||||||
indicators := ""
|
|
||||||
if r.HasWitness {
|
|
||||||
indicators += " " + AgentTypeIcons[AgentWitness]
|
|
||||||
}
|
|
||||||
if r.HasRefinery {
|
|
||||||
indicators += " " + AgentTypeIcons[AgentRefinery]
|
|
||||||
}
|
|
||||||
if r.CrewCount > 0 {
|
|
||||||
indicators += " " + AgentTypeIcons[AgentCrew]
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Printf(" %s%s\n", style.Bold.Render(r.Name), indicators)
|
// Show all agents with their runtime state
|
||||||
|
for _, agent := range r.Agents {
|
||||||
if len(r.Polecats) > 0 {
|
statusStr := style.Success.Render("✓ running")
|
||||||
fmt.Printf(" Polecats: %v\n", r.Polecats)
|
if !agent.Running {
|
||||||
} else {
|
statusStr = style.Error.Render("✗ stopped")
|
||||||
fmt.Printf(" %s\n", style.Dim.Render("No polecats"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(r.Crews) > 0 {
|
|
||||||
fmt.Printf(" Crews: %v\n", r.Crews)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show active hooks
|
|
||||||
activeHooks := []AgentHookInfo{}
|
|
||||||
for _, h := range r.Hooks {
|
|
||||||
if h.HasWork {
|
|
||||||
activeHooks = append(activeHooks, h)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
if len(activeHooks) > 0 {
|
// Find hook info for this agent
|
||||||
fmt.Printf(" %s\n", style.Bold.Render("Hooks:"))
|
hookInfo := ""
|
||||||
for _, h := range activeHooks {
|
for _, h := range r.Hooks {
|
||||||
if h.Molecule != "" {
|
if h.Agent == agent.Address && h.HasWork {
|
||||||
fmt.Printf(" %s %s → %s\n", AgentTypeIcons[AgentPolecat], h.Agent, h.Molecule)
|
if h.Molecule != "" {
|
||||||
} else if h.Title != "" {
|
hookInfo = fmt.Sprintf(" → %s", h.Molecule)
|
||||||
fmt.Printf(" %s %s → %s\n", AgentTypeIcons[AgentPolecat], h.Agent, h.Title)
|
} else if h.Title != "" {
|
||||||
} else {
|
hookInfo = fmt.Sprintf(" → %s", h.Title)
|
||||||
fmt.Printf(" %s %s → (work attached)\n", AgentTypeIcons[AgentPolecat], h.Agent)
|
} else {
|
||||||
|
hookInfo = " → (work attached)"
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Format agent name based on role
|
||||||
|
displayName := agent.Name
|
||||||
|
if agent.Role == "crew" {
|
||||||
|
displayName = "crew/" + agent.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf(" %-14s %s%s\n", displayName, statusStr, hookInfo)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Show polecats if any (these are already in r.Agents if discovered)
|
||||||
|
if len(r.Polecats) == 0 && len(r.Crews) == 0 && !r.HasWitness && !r.HasRefinery {
|
||||||
|
fmt.Printf(" %s\n", style.Dim.Render("No agents"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,6 +289,92 @@ func discoverRigHooks(r *rig.Rig, crews []string) []AgentHookInfo {
|
|||||||
return hooks
|
return hooks
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// discoverGlobalAgents checks runtime state for town-level agents (Mayor, Deacon).
|
||||||
|
func discoverGlobalAgents(t *tmux.Tmux) []AgentRuntime {
|
||||||
|
var agents []AgentRuntime
|
||||||
|
|
||||||
|
// Check Mayor
|
||||||
|
mayorRunning, _ := t.HasSession(MayorSessionName)
|
||||||
|
agents = append(agents, AgentRuntime{
|
||||||
|
Name: "mayor",
|
||||||
|
Address: "mayor",
|
||||||
|
Session: MayorSessionName,
|
||||||
|
Role: "coordinator",
|
||||||
|
Running: mayorRunning,
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check Deacon
|
||||||
|
deaconRunning, _ := t.HasSession(DeaconSessionName)
|
||||||
|
agents = append(agents, AgentRuntime{
|
||||||
|
Name: "deacon",
|
||||||
|
Address: "deacon",
|
||||||
|
Session: DeaconSessionName,
|
||||||
|
Role: "health-check",
|
||||||
|
Running: deaconRunning,
|
||||||
|
})
|
||||||
|
|
||||||
|
return agents
|
||||||
|
}
|
||||||
|
|
||||||
|
// discoverRigAgents checks runtime state for all agents in a rig.
|
||||||
|
func discoverRigAgents(t *tmux.Tmux, r *rig.Rig, crews []string) []AgentRuntime {
|
||||||
|
var agents []AgentRuntime
|
||||||
|
|
||||||
|
// Check Witness
|
||||||
|
if r.HasWitness {
|
||||||
|
sessionName := witnessSessionName(r.Name)
|
||||||
|
running, _ := t.HasSession(sessionName)
|
||||||
|
agents = append(agents, AgentRuntime{
|
||||||
|
Name: "witness",
|
||||||
|
Address: r.Name + "/witness",
|
||||||
|
Session: sessionName,
|
||||||
|
Role: "witness",
|
||||||
|
Running: running,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Refinery
|
||||||
|
if r.HasRefinery {
|
||||||
|
sessionName := fmt.Sprintf("gt-%s-refinery", r.Name)
|
||||||
|
running, _ := t.HasSession(sessionName)
|
||||||
|
agents = append(agents, AgentRuntime{
|
||||||
|
Name: "refinery",
|
||||||
|
Address: r.Name + "/refinery",
|
||||||
|
Session: sessionName,
|
||||||
|
Role: "refinery",
|
||||||
|
Running: running,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Polecats
|
||||||
|
for _, name := range r.Polecats {
|
||||||
|
sessionName := fmt.Sprintf("gt-%s-%s", r.Name, name)
|
||||||
|
running, _ := t.HasSession(sessionName)
|
||||||
|
agents = append(agents, AgentRuntime{
|
||||||
|
Name: name,
|
||||||
|
Address: r.Name + "/" + name,
|
||||||
|
Session: sessionName,
|
||||||
|
Role: "polecat",
|
||||||
|
Running: running,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Crew
|
||||||
|
for _, name := range crews {
|
||||||
|
sessionName := crewSessionName(r.Name, name)
|
||||||
|
running, _ := t.HasSession(sessionName)
|
||||||
|
agents = append(agents, AgentRuntime{
|
||||||
|
Name: name,
|
||||||
|
Address: r.Name + "/crew/" + name,
|
||||||
|
Session: sessionName,
|
||||||
|
Role: "crew",
|
||||||
|
Running: running,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return agents
|
||||||
|
}
|
||||||
|
|
||||||
// getAgentHook retrieves hook status for a specific agent.
|
// getAgentHook retrieves hook status for a specific agent.
|
||||||
func getAgentHook(b *beads.Beads, role, agentAddress, roleType string) AgentHookInfo {
|
func getAgentHook(b *beads.Beads, role, agentAddress, roleType string) AgentHookInfo {
|
||||||
hook := AgentHookInfo{
|
hook := AgentHookInfo{
|
||||||
|
|||||||
Reference in New Issue
Block a user