diff --git a/internal/cmd/agents.go b/internal/cmd/agents.go index a8487e51..c996d871 100644 --- a/internal/cmd/agents.go +++ b/internal/cmd/agents.go @@ -35,7 +35,6 @@ type AgentSession struct { Type AgentType Rig string // For rig-specific agents AgentName string // e.g., crew name, polecat name - Town string // For mayor/deacon only (town name from session) } // AgentTypeColors maps agent types to tmux color codes. @@ -136,16 +135,13 @@ func categorizeSession(name string) *AgentSession { session := &AgentSession{Name: name} suffix := strings.TrimPrefix(name, "gt-") - // Town-level agents: gt-{town}-mayor, gt-{town}-deacon - // Check if suffix ends with -mayor or -deacon (new format) - if strings.HasSuffix(suffix, "-mayor") { + // Town-level agents: gt-mayor, gt-deacon (simple format, one per machine) + if suffix == "mayor" { session.Type = AgentMayor - session.Town = strings.TrimSuffix(suffix, "-mayor") return session } - if strings.HasSuffix(suffix, "-deacon") { + if suffix == "deacon" { session.Type = AgentDeacon - session.Town = strings.TrimSuffix(suffix, "-deacon") return session } diff --git a/internal/cmd/deacon.go b/internal/cmd/deacon.go index eb53b063..95f0311d 100644 --- a/internal/cmd/deacon.go +++ b/internal/cmd/deacon.go @@ -21,18 +21,9 @@ import ( "github.com/steveyegge/gastown/internal/workspace" ) -// getDeaconSessionName returns the Deacon session name for the current workspace. -// The session name includes the town name to avoid collisions between multiple HQs. +// getDeaconSessionName returns the Deacon session name. func getDeaconSessionName() (string, error) { - townRoot, err := workspace.FindFromCwdOrError() - if err != nil { - return "", fmt.Errorf("not in a Gas Town workspace: %w", err) - } - townName, err := workspace.GetTownName(townRoot) - if err != nil { - return "", err - } - return session.DeaconSessionName(townName), nil + return session.DeaconSessionName(), nil } var deaconCmd = &cobra.Command{ @@ -673,14 +664,8 @@ func runDeaconHealthCheck(cmd *cobra.Command, args []string) error { return nil } - // Get town name for session name generation - townName, err := workspace.GetTownName(townRoot) - if err != nil { - return fmt.Errorf("getting town name: %w", err) - } - // Get agent bead info before ping (for baseline) - beadID, sessionName, err := agentAddressToIDs(agent, townName) + beadID, sessionName, err := agentAddressToIDs(agent) if err != nil { return fmt.Errorf("invalid agent address: %w", err) } @@ -787,14 +772,8 @@ func runDeaconForceKill(cmd *cobra.Command, args []string) error { agent, remaining.Round(time.Second)) } - // Get town name for session name generation - townName, err := workspace.GetTownName(townRoot) - if err != nil { - return fmt.Errorf("getting town name: %w", err) - } - // Get session name - _, sessionName, err := agentAddressToIDs(agent, townName) + _, sessionName, err := agentAddressToIDs(agent) if err != nil { return fmt.Errorf("invalid agent address: %w", err) } @@ -1154,14 +1133,13 @@ func notifyMayorOfWitnessFailure(townRoot string, zombies []zombieInfo) { // agentAddressToIDs converts an agent address to bead ID and session name. // Supports formats: "gastown/polecats/max", "gastown/witness", "deacon", "mayor" -// The townName parameter is required for mayor/deacon to generate correct session names. -func agentAddressToIDs(address, townName string) (beadID, sessionName string, err error) { +func agentAddressToIDs(address string) (beadID, sessionName string, err error) { switch address { case "deacon": - sessName := session.DeaconSessionName(townName) + sessName := session.DeaconSessionName() return sessName, sessName, nil case "mayor": - sessName := session.MayorSessionName(townName) + sessName := session.MayorSessionName() return sessName, sessName, nil } @@ -1227,11 +1205,7 @@ func sendMail(townRoot, to, subject, body string) { // updateAgentBeadState updates an agent bead's state. func updateAgentBeadState(townRoot, agent, state, reason string) { - townName, err := workspace.GetTownName(townRoot) - if err != nil { - return - } - beadID, _, err := agentAddressToIDs(agent, townName) + beadID, _, err := agentAddressToIDs(agent) if err != nil { return } diff --git a/internal/cmd/dnd_test.go b/internal/cmd/dnd_test.go index 11274bf1..d9d9ee8a 100644 --- a/internal/cmd/dnd_test.go +++ b/internal/cmd/dnd_test.go @@ -5,13 +5,13 @@ import ( ) func TestAddressToAgentBeadID(t *testing.T) { - townName := "ai" tests := []struct { address string expected string }{ - {"mayor", "gt-ai-mayor"}, - {"deacon", "gt-ai-deacon"}, + // Mayor and deacon use simple session names (no town qualifier) + {"mayor", "gt-mayor"}, + {"deacon", "gt-deacon"}, {"gastown/witness", "gt-gastown-witness"}, {"gastown/refinery", "gt-gastown-refinery"}, {"gastown/alpha", "gt-gastown-polecat-alpha"}, @@ -25,9 +25,9 @@ func TestAddressToAgentBeadID(t *testing.T) { for _, tt := range tests { t.Run(tt.address, func(t *testing.T) { - got := addressToAgentBeadID(tt.address, townName) + got := addressToAgentBeadID(tt.address) if got != tt.expected { - t.Errorf("addressToAgentBeadID(%q, %q) = %q, want %q", tt.address, townName, got, tt.expected) + t.Errorf("addressToAgentBeadID(%q) = %q, want %q", tt.address, got, tt.expected) } }) } diff --git a/internal/cmd/install.go b/internal/cmd/install.go index 7c723021..97028bec 100644 --- a/internal/cmd/install.go +++ b/internal/cmd/install.go @@ -261,8 +261,8 @@ func createMayorCLAUDEmd(hqRoot, townRoot string) error { TownRoot: townRoot, TownName: townName, WorkDir: hqRoot, - MayorSession: session.MayorSessionName(townName), - DeaconSession: session.DeaconSessionName(townName), + MayorSession: session.MayorSessionName(), + DeaconSession: session.DeaconSessionName(), } content, err := tmpl.RenderRole("mayor", data) diff --git a/internal/cmd/mayor.go b/internal/cmd/mayor.go index bada2f94..f7745873 100644 --- a/internal/cmd/mayor.go +++ b/internal/cmd/mayor.go @@ -14,18 +14,9 @@ import ( "github.com/steveyegge/gastown/internal/workspace" ) -// getMayorSessionName returns the Mayor session name for the current workspace. -// The session name includes the town name to avoid collisions between multiple HQs. +// getMayorSessionName returns the Mayor session name. func getMayorSessionName() (string, error) { - townRoot, err := workspace.FindFromCwdOrError() - if err != nil { - return "", fmt.Errorf("not in a Gas Town workspace: %w", err) - } - townName, err := workspace.GetTownName(townRoot) - if err != nil { - return "", err - } - return session.MayorSessionName(townName), nil + return session.MayorSessionName(), nil } var mayorCmd = &cobra.Command{ diff --git a/internal/cmd/nudge.go b/internal/cmd/nudge.go index e1e57ac5..62e21947 100644 --- a/internal/cmd/nudge.go +++ b/internal/cmd/nudge.go @@ -120,17 +120,11 @@ func runNudge(cmd *cobra.Command, args []string) error { t := tmux.NewTmux() - // Get session names for this town - townName := "" - if townRoot != "" { - townName, _ = workspace.GetTownName(townRoot) - } - // Expand role shortcuts to session names - // These shortcuts let users type "mayor" instead of "gt-{town}-mayor" + // These shortcuts let users type "mayor" instead of "gt-mayor" switch target { case "mayor": - target = session.MayorSessionName(townName) + target = session.MayorSessionName() case "witness", "refinery": // These need the current rig roleInfo, err := GetRole() @@ -149,7 +143,7 @@ func runNudge(cmd *cobra.Command, args []string) error { // Special case: "deacon" target maps to the Deacon session if target == "deacon" { - deaconSession := session.DeaconSessionName(townName) + deaconSession := session.DeaconSessionName() // Check if Deacon session exists exists, err := t.HasSession(deaconSession) if err != nil { @@ -286,9 +280,6 @@ func runNudgeChannel(channelName, message string) error { // Prefix message with sender prefixedMessage := fmt.Sprintf("[from %s] %s", sender, message) - // Get town name for session names - townName, _ := workspace.GetTownName(townRoot) - // Get all running sessions for pattern matching agents, err := getAgentSessions(true) if err != nil { @@ -300,7 +291,7 @@ func runNudgeChannel(channelName, message string) error { seenTargets := make(map[string]bool) for _, pattern := range patterns { - resolved := resolveNudgePattern(pattern, agents, townName) + resolved := resolveNudgePattern(pattern, agents) for _, sessionName := range resolved { if !seenTargets[sessionName] { seenTargets[sessionName] = true @@ -362,15 +353,15 @@ func runNudgeChannel(channelName, message string) error { // - Role: "*/witness" → all witness sessions // - Special: "mayor", "deacon" → gt-{town}-mayor, gt-{town}-deacon // townName is used to generate the correct session names for mayor/deacon. -func resolveNudgePattern(pattern string, agents []*AgentSession, townName string) []string { +func resolveNudgePattern(pattern string, agents []*AgentSession) []string { var results []string // Handle special cases switch pattern { case "mayor": - return []string{session.MayorSessionName(townName)} + return []string{session.MayorSessionName()} case "deacon": - return []string{session.DeaconSessionName(townName)} + return []string{session.DeaconSessionName()} } // Parse pattern @@ -438,11 +429,8 @@ func shouldNudgeTarget(townRoot, targetAddress string, force bool) (bool, string return true, "", nil } - // Get town name for session name generation - townName, _ := workspace.GetTownName(townRoot) - // Try to determine agent bead ID from address - agentBeadID := addressToAgentBeadID(targetAddress, townName) + agentBeadID := addressToAgentBeadID(targetAddress) if agentBeadID == "" { // Can't determine agent bead, allow the nudge return true, "", nil @@ -467,13 +455,13 @@ func shouldNudgeTarget(townRoot, targetAddress string, force bool) (bool, string // - "gastown/alpha" -> "gt-gastown-polecat-alpha" // // Returns empty string if the address cannot be converted. -func addressToAgentBeadID(address, townName string) string { +func addressToAgentBeadID(address string) string { // Handle special cases switch address { case "mayor": - return session.MayorSessionName(townName) + return session.MayorSessionName() case "deacon": - return session.DeaconSessionName(townName) + return session.DeaconSessionName() } // Parse rig/role format diff --git a/internal/cmd/nudge_test.go b/internal/cmd/nudge_test.go index d9203a32..176b8f64 100644 --- a/internal/cmd/nudge_test.go +++ b/internal/cmd/nudge_test.go @@ -5,10 +5,10 @@ import ( ) func TestResolveNudgePattern(t *testing.T) { - // Create test agent sessions + // Create test agent sessions (no Town field for mayor/deacon anymore) agents := []*AgentSession{ - {Name: "gt-ai-mayor", Type: AgentMayor, Town: "ai"}, - {Name: "gt-ai-deacon", Type: AgentDeacon, Town: "ai"}, + {Name: "gt-mayor", Type: AgentMayor}, + {Name: "gt-deacon", Type: AgentDeacon}, {Name: "gt-gastown-witness", Type: AgentWitness, Rig: "gastown"}, {Name: "gt-gastown-refinery", Type: AgentRefinery, Rig: "gastown"}, {Name: "gt-gastown-crew-max", Type: AgentCrew, Rig: "gastown", AgentName: "max"}, @@ -27,12 +27,12 @@ func TestResolveNudgePattern(t *testing.T) { { name: "mayor special case", pattern: "mayor", - expected: []string{"gt-ai-mayor"}, + expected: []string{"gt-mayor"}, }, { name: "deacon special case", pattern: "deacon", - expected: []string{"gt-ai-deacon"}, + expected: []string{"gt-deacon"}, }, { name: "specific witness", @@ -86,10 +86,9 @@ func TestResolveNudgePattern(t *testing.T) { }, } - townName := "ai" for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - got := resolveNudgePattern(tt.pattern, agents, townName) + got := resolveNudgePattern(tt.pattern, agents) if len(got) != len(tt.expected) { t.Errorf("resolveNudgePattern(%q) returned %d results, want %d: got %v, want %v", diff --git a/internal/cmd/prime.go b/internal/cmd/prime.go index a3abe29b..a20b909b 100644 --- a/internal/cmd/prime.go +++ b/internal/cmd/prime.go @@ -315,8 +315,8 @@ func outputPrimeContext(ctx RoleContext) error { TownName: townName, WorkDir: ctx.WorkDir, Polecat: ctx.Polecat, - MayorSession: session.MayorSessionName(townName), - DeaconSession: session.DeaconSessionName(townName), + MayorSession: session.MayorSessionName(), + DeaconSession: session.DeaconSessionName(), } // Render and output diff --git a/internal/cmd/statusline_test.go b/internal/cmd/statusline_test.go index 5895918d..5f31d337 100644 --- a/internal/cmd/statusline_test.go +++ b/internal/cmd/statusline_test.go @@ -31,8 +31,8 @@ func TestCategorizeSessionRig(t *testing.T) { {"gt-a-b", "a"}, // minimum valid // Town-level agents (no rig) - {"gt-ai-mayor", ""}, - {"gt-ai-deacon", ""}, + {"gt-mayor", ""}, + {"gt-deacon", ""}, } for _, tt := range tests { @@ -68,8 +68,8 @@ func TestCategorizeSessionType(t *testing.T) { {"gt-myrig-crew-user", AgentCrew}, // Town-level agents - {"gt-ai-mayor", AgentMayor}, - {"gt-ai-deacon", AgentDeacon}, + {"gt-mayor", AgentMayor}, + {"gt-deacon", AgentDeacon}, } for _, tt := range tests { diff --git a/internal/cmd/theme.go b/internal/cmd/theme.go index 3a6ec427..9cab0c82 100644 --- a/internal/cmd/theme.go +++ b/internal/cmd/theme.go @@ -117,15 +117,9 @@ func runThemeApply(cmd *cobra.Command, args []string) error { // Determine current rig rigName := detectCurrentRig() - // Get town name for session name comparison - var mayorSession, deaconSession string - townRoot, err := workspace.FindFromCwd() - if err == nil && townRoot != "" { - if townName, err := workspace.GetTownName(townRoot); err == nil { - mayorSession = session.MayorSessionName(townName) - deaconSession = session.DeaconSessionName(townName) - } - } + // Get session names for comparison + mayorSession := session.MayorSessionName() + deaconSession := session.DeaconSessionName() // Apply to matching sessions applied := 0 diff --git a/internal/constants/constants.go b/internal/constants/constants.go index a402e409..85d99186 100644 --- a/internal/constants/constants.go +++ b/internal/constants/constants.go @@ -90,8 +90,8 @@ const ( ) // Tmux session names. -// Note: Mayor and Deacon session names are now dynamic (include town name). -// Use session.MayorSessionName(townName) and session.DeaconSessionName(townName). +// Mayor and Deacon use simple session names: gt-mayor, gt-deacon (one per machine). +// Use session.MayorSessionName() and session.DeaconSessionName(). const ( // SessionPrefix is the prefix for all Gas Town tmux sessions. SessionPrefix = "gt-" diff --git a/internal/daemon/daemon.go b/internal/daemon/daemon.go index 2f5cae7e..ab6f68ba 100644 --- a/internal/daemon/daemon.go +++ b/internal/daemon/daemon.go @@ -22,7 +22,6 @@ import ( "github.com/steveyegge/gastown/internal/polecat" "github.com/steveyegge/gastown/internal/session" "github.com/steveyegge/gastown/internal/tmux" - "github.com/steveyegge/gastown/internal/workspace" ) // Daemon is the town-level background service. @@ -198,13 +197,7 @@ const DeaconRole = "deacon" // getDeaconSessionName returns the Deacon session name for the daemon's town. func (d *Daemon) getDeaconSessionName() string { - townName, err := workspace.GetTownName(d.config.TownRoot) - if err != nil { - // Fallback to legacy name if town config can't be loaded - d.logger.Printf("Warning: failed to get town name: %v, using fallback", err) - return "gt-deacon" - } - return session.DeaconSessionName(townName) + return session.DeaconSessionName() } // ensureBootRunning spawns Boot to triage the Deacon. diff --git a/internal/daemon/lifecycle.go b/internal/daemon/lifecycle.go index 060aab51..3f944c25 100644 --- a/internal/daemon/lifecycle.go +++ b/internal/daemon/lifecycle.go @@ -14,7 +14,6 @@ import ( "github.com/steveyegge/gastown/internal/constants" "github.com/steveyegge/gastown/internal/session" "github.com/steveyegge/gastown/internal/tmux" - "github.com/steveyegge/gastown/internal/workspace" ) // BeadsMessage represents a message from gt mail inbox --json. @@ -313,15 +312,9 @@ func (d *Daemon) identityToSession(identity string) string { // Fallback: use default patterns based on role type switch parsed.RoleType { case "mayor": - if townName, err := workspace.GetTownName(d.config.TownRoot); err == nil { - return session.MayorSessionName(townName) - } - return "" + return session.MayorSessionName() case "deacon": - if townName, err := workspace.GetTownName(d.config.TownRoot); err == nil { - return session.DeaconSessionName(townName) - } - return "" + return session.DeaconSessionName() case "witness", "refinery": return fmt.Sprintf("gt-%s-%s", parsed.RigName, parsed.RoleType) case "crew": diff --git a/internal/daemon/lifecycle_test.go b/internal/daemon/lifecycle_test.go index e75776c9..29c62343 100644 --- a/internal/daemon/lifecycle_test.go +++ b/internal/daemon/lifecycle_test.go @@ -184,9 +184,10 @@ func TestIdentityToSession_Mayor(t *testing.T) { d, cleanup := testDaemonWithTown(t, "ai") defer cleanup() + // Mayor session name is now fixed (one per machine, no town qualifier) result := d.identityToSession("mayor") - if result != "gt-ai-mayor" { - t.Errorf("identityToSession('mayor') = %q, expected 'gt-ai-mayor'", result) + if result != "gt-mayor" { + t.Errorf("identityToSession('mayor') = %q, expected 'gt-mayor'", result) } } diff --git a/internal/doctor/lifecycle_check.go b/internal/doctor/lifecycle_check.go index fcc7cc8d..375e0954 100644 --- a/internal/doctor/lifecycle_check.go +++ b/internal/doctor/lifecycle_check.go @@ -9,7 +9,6 @@ import ( "strings" "github.com/steveyegge/gastown/internal/session" - "github.com/steveyegge/gastown/internal/workspace" ) // LifecycleHygieneCheck detects and cleans up stale lifecycle state. @@ -243,12 +242,7 @@ func (c *LifecycleHygieneCheck) isSessionHealthy(identity, townRoot string) bool func identityToSessionName(identity, townRoot string) string { switch identity { case "mayor": - if townRoot != "" { - if townName, err := workspace.GetTownName(townRoot); err == nil { - return session.MayorSessionName(townName) - } - } - return "" // Cannot generate session name without town root + return session.MayorSessionName() default: if strings.HasSuffix(identity, "-witness") || strings.HasSuffix(identity, "-refinery") || diff --git a/internal/doctor/orphan_check.go b/internal/doctor/orphan_check.go index bb97e89f..93c268f3 100644 --- a/internal/doctor/orphan_check.go +++ b/internal/doctor/orphan_check.go @@ -10,7 +10,6 @@ import ( "github.com/steveyegge/gastown/internal/session" "github.com/steveyegge/gastown/internal/tmux" - "github.com/steveyegge/gastown/internal/workspace" ) // OrphanSessionCheck detects orphaned tmux sessions that don't match @@ -57,12 +56,9 @@ func (c *OrphanSessionCheck) Run(ctx *CheckContext) *CheckResult { // Get list of valid rigs validRigs := c.getValidRigs(ctx.TownRoot) - // Get dynamic session names for mayor/deacon - var mayorSession, deaconSession string - if townName, err := workspace.GetTownName(ctx.TownRoot); err == nil { - mayorSession = session.MayorSessionName(townName) - deaconSession = session.DeaconSessionName(townName) - } + // Get session names for mayor/deacon + mayorSession := session.MayorSessionName() + deaconSession := session.DeaconSessionName() // Check each session var orphans []string diff --git a/internal/doctor/tmux_check.go b/internal/doctor/tmux_check.go index 38e07ae1..173e554c 100644 --- a/internal/doctor/tmux_check.go +++ b/internal/doctor/tmux_check.go @@ -7,7 +7,6 @@ import ( "github.com/steveyegge/gastown/internal/session" "github.com/steveyegge/gastown/internal/tmux" - "github.com/steveyegge/gastown/internal/workspace" ) // LinkedPaneCheck detects tmux sessions that share panes, @@ -86,11 +85,7 @@ func (c *LinkedPaneCheck) Run(ctx *CheckContext) *CheckResult { } // Cache for Fix (exclude mayor session since we don't want to kill it) - // Get dynamic mayor session name - var mayorSession string - if townName, err := workspace.GetTownName(ctx.TownRoot); err == nil { - mayorSession = session.MayorSessionName(townName) - } + mayorSession := session.MayorSessionName() c.linkedSessions = nil for sess := range linkedSessionSet { diff --git a/internal/mail/router.go b/internal/mail/router.go index da83aed8..7306bf19 100644 --- a/internal/mail/router.go +++ b/internal/mail/router.go @@ -13,7 +13,6 @@ import ( "github.com/steveyegge/gastown/internal/config" "github.com/steveyegge/gastown/internal/session" "github.com/steveyegge/gastown/internal/tmux" - "github.com/steveyegge/gastown/internal/workspace" ) // ErrUnknownList indicates a mailing list name was not found in configuration. @@ -936,13 +935,7 @@ func (r *Router) GetMailbox(address string) (*Mailbox, error) { // Uses send-keys to echo a visible banner to ensure notification is seen. // Supports mayor/, rig/polecat, and rig/refinery addresses. func (r *Router) notifyRecipient(msg *Message) error { - // Get town name for session name generation - var townName string - if r.townRoot != "" { - townName, _ = workspace.GetTownName(r.townRoot) - } - - sessionID := addressToSessionID(msg.To, townName) + sessionID := addressToSessionID(msg.To) if sessionID == "" { return nil // Unable to determine session ID } @@ -959,21 +952,15 @@ func (r *Router) notifyRecipient(msg *Message) error { // addressToSessionID converts a mail address to a tmux session ID. // Returns empty string if address format is not recognized. -func addressToSessionID(address, townName string) string { +func addressToSessionID(address string) string { // Mayor address: "mayor/" or "mayor" if strings.HasPrefix(address, "mayor") { - if townName != "" { - return session.MayorSessionName(townName) - } - return "" // Cannot generate session name without town name + return session.MayorSessionName() } // Deacon address: "deacon/" or "deacon" if strings.HasPrefix(address, "deacon") { - if townName != "" { - return session.DeaconSessionName(townName) - } - return "" // Cannot generate session name without town name + return session.DeaconSessionName() } // Rig-based address: "rig/target" diff --git a/internal/mail/router_test.go b/internal/mail/router_test.go index c6556dab..bd4cf09b 100644 --- a/internal/mail/router_test.go +++ b/internal/mail/router_test.go @@ -87,14 +87,13 @@ func TestIsTownLevelAddress(t *testing.T) { } func TestAddressToSessionID(t *testing.T) { - townName := "ai" tests := []struct { address string want string }{ - {"mayor", "gt-ai-mayor"}, - {"mayor/", "gt-ai-mayor"}, - {"deacon", "gt-ai-deacon"}, + {"mayor", "gt-mayor"}, + {"mayor/", "gt-mayor"}, + {"deacon", "gt-deacon"}, {"gastown/refinery", "gt-gastown-refinery"}, {"gastown/Toast", "gt-gastown-Toast"}, {"beads/witness", "gt-beads-witness"}, @@ -105,9 +104,9 @@ func TestAddressToSessionID(t *testing.T) { for _, tt := range tests { t.Run(tt.address, func(t *testing.T) { - got := addressToSessionID(tt.address, townName) + got := addressToSessionID(tt.address) if got != tt.want { - t.Errorf("addressToSessionID(%q, %q) = %q, want %q", tt.address, townName, got, tt.want) + t.Errorf("addressToSessionID(%q) = %q, want %q", tt.address, got, tt.want) } }) } diff --git a/internal/session/identity.go b/internal/session/identity.go index 7978b0d8..1ac72b5d 100644 --- a/internal/session/identity.go +++ b/internal/session/identity.go @@ -21,7 +21,6 @@ const ( // AgentIdentity represents a parsed Gas Town agent identity. type AgentIdentity struct { Role Role // mayor, deacon, witness, refinery, crew, polecat - Town string // town name (for mayor/deacon only) Rig string // rig name (empty for mayor/deacon) Name string // crew/polecat name (empty for mayor/deacon/witness/refinery) } @@ -29,8 +28,8 @@ type AgentIdentity struct { // ParseSessionName parses a tmux session name into an AgentIdentity. // // Session name formats: -// - gt--mayor → Role: mayor, Town: -// - gt--deacon → Role: deacon, Town: +// - gt-mayor → Role: mayor (one per machine) +// - gt-deacon → Role: deacon (one per machine) // - gt--witness → Role: witness, Rig: // - gt--refinery → Role: refinery, Rig: // - gt--crew- → Role: crew, Rig: , Name: @@ -49,20 +48,18 @@ func ParseSessionName(session string) (*AgentIdentity, error) { return nil, fmt.Errorf("invalid session name %q: empty after prefix", session) } - // Parse into parts - parts := strings.Split(suffix, "-") - if len(parts) < 2 { - return nil, fmt.Errorf("invalid session name %q: expected town-role or rig-role format", session) + // Check for simple town-level roles (no rig qualifier) + if suffix == "mayor" { + return &AgentIdentity{Role: RoleMayor}, nil + } + if suffix == "deacon" { + return &AgentIdentity{Role: RoleDeacon}, nil } - // Check for mayor/deacon (town-level roles with suffix marker) - if parts[len(parts)-1] == "mayor" { - town := strings.Join(parts[:len(parts)-1], "-") - return &AgentIdentity{Role: RoleMayor, Town: town}, nil - } - if parts[len(parts)-1] == "deacon" { - town := strings.Join(parts[:len(parts)-1], "-") - return &AgentIdentity{Role: RoleDeacon, Town: town}, nil + // Parse into parts for rig-level roles + parts := strings.Split(suffix, "-") + if len(parts) < 2 { + return nil, fmt.Errorf("invalid session name %q: expected rig-role format", session) } // Check for witness/refinery (suffix markers) @@ -97,9 +94,9 @@ func ParseSessionName(session string) (*AgentIdentity, error) { func (a *AgentIdentity) SessionName() string { switch a.Role { case RoleMayor: - return MayorSessionName(a.Town) + return MayorSessionName() case RoleDeacon: - return DeaconSessionName(a.Town) + return DeaconSessionName() case RoleWitness: return WitnessSessionName(a.Rig) case RoleRefinery: diff --git a/internal/session/identity_test.go b/internal/session/identity_test.go index d15f420a..3fb02de1 100644 --- a/internal/session/identity_test.go +++ b/internal/session/identity_test.go @@ -6,38 +6,23 @@ import ( func TestParseSessionName(t *testing.T) { tests := []struct { - name string - session string - wantRole Role - wantTown string - wantRig string - wantName string - wantErr bool + name string + session string + wantRole Role + wantRig string + wantName string + wantErr bool }{ - // Town-level roles (mayor/deacon with town name) + // Town-level roles (simple gt-mayor, gt-deacon) { - name: "mayor simple town", - session: "gt-ai-mayor", + name: "mayor", + session: "gt-mayor", wantRole: RoleMayor, - wantTown: "ai", }, { - name: "mayor hyphenated town", - session: "gt-my-town-mayor", - wantRole: RoleMayor, - wantTown: "my-town", - }, - { - name: "deacon simple town", - session: "gt-alpha-deacon", + name: "deacon", + session: "gt-deacon", wantRole: RoleDeacon, - wantTown: "alpha", - }, - { - name: "deacon hyphenated town", - session: "gt-my-town-deacon", - wantRole: RoleDeacon, - wantTown: "my-town", }, // Witness (simple rig) @@ -138,9 +123,6 @@ func TestParseSessionName(t *testing.T) { if got.Role != tt.wantRole { t.Errorf("ParseSessionName(%q).Role = %v, want %v", tt.session, got.Role, tt.wantRole) } - if got.Town != tt.wantTown { - t.Errorf("ParseSessionName(%q).Town = %v, want %v", tt.session, got.Town, tt.wantTown) - } if got.Rig != tt.wantRig { t.Errorf("ParseSessionName(%q).Rig = %v, want %v", tt.session, got.Rig, tt.wantRig) } @@ -158,19 +140,14 @@ func TestAgentIdentity_SessionName(t *testing.T) { want string }{ { - name: "mayor with town", - identity: AgentIdentity{Role: RoleMayor, Town: "ai"}, - want: "gt-ai-mayor", + name: "mayor", + identity: AgentIdentity{Role: RoleMayor}, + want: "gt-mayor", }, { - name: "mayor hyphenated town", - identity: AgentIdentity{Role: RoleMayor, Town: "my-town"}, - want: "gt-my-town-mayor", - }, - { - name: "deacon with town", - identity: AgentIdentity{Role: RoleDeacon, Town: "alpha"}, - want: "gt-alpha-deacon", + name: "deacon", + identity: AgentIdentity{Role: RoleDeacon}, + want: "gt-deacon", }, { name: "witness", @@ -253,8 +230,8 @@ func TestAgentIdentity_Address(t *testing.T) { func TestParseSessionName_RoundTrip(t *testing.T) { // Test that parsing then reconstructing gives the same result sessions := []string{ - "gt-ai-mayor", - "gt-alpha-deacon", + "gt-mayor", + "gt-deacon", "gt-gastown-witness", "gt-foo-bar-refinery", "gt-gastown-crew-max", diff --git a/internal/session/names.go b/internal/session/names.go index 77cd373d..919a6207 100644 --- a/internal/session/names.go +++ b/internal/session/names.go @@ -12,17 +12,15 @@ import ( const Prefix = "gt-" // MayorSessionName returns the session name for the Mayor agent. -// The townName parameter allows multiple towns to run concurrently -// without tmux session name collisions. -func MayorSessionName(townName string) string { - return fmt.Sprintf("%s%s-mayor", Prefix, townName) +// One mayor per machine - multi-town requires containers/VMs for isolation. +func MayorSessionName() string { + return Prefix + "mayor" } // DeaconSessionName returns the session name for the Deacon agent. -// The townName parameter allows multiple towns to run concurrently -// without tmux session name collisions. -func DeaconSessionName(townName string) string { - return fmt.Sprintf("%s%s-deacon", Prefix, townName) +// One deacon per machine - multi-town requires containers/VMs for isolation. +func DeaconSessionName() string { + return Prefix + "deacon" } // WitnessSessionName returns the session name for a rig's Witness agent. diff --git a/internal/session/names_test.go b/internal/session/names_test.go index f3191e09..a4949600 100644 --- a/internal/session/names_test.go +++ b/internal/session/names_test.go @@ -8,42 +8,20 @@ import ( ) func TestMayorSessionName(t *testing.T) { - tests := []struct { - townName string - want string - }{ - {"ai", "gt-ai-mayor"}, - {"alpha", "gt-alpha-mayor"}, - {"gastown", "gt-gastown-mayor"}, - {"", "gt--mayor"}, // empty town name - } - for _, tt := range tests { - t.Run(tt.townName, func(t *testing.T) { - got := MayorSessionName(tt.townName) - if got != tt.want { - t.Errorf("MayorSessionName(%q) = %q, want %q", tt.townName, got, tt.want) - } - }) + // Mayor session name is now fixed (one per machine) + want := "gt-mayor" + got := MayorSessionName() + if got != want { + t.Errorf("MayorSessionName() = %q, want %q", got, want) } } func TestDeaconSessionName(t *testing.T) { - tests := []struct { - townName string - want string - }{ - {"ai", "gt-ai-deacon"}, - {"alpha", "gt-alpha-deacon"}, - {"gastown", "gt-gastown-deacon"}, - {"", "gt--deacon"}, // empty town name - } - for _, tt := range tests { - t.Run(tt.townName, func(t *testing.T) { - got := DeaconSessionName(tt.townName) - if got != tt.want { - t.Errorf("DeaconSessionName(%q) = %q, want %q", tt.townName, got, tt.want) - } - }) + // Deacon session name is now fixed (one per machine) + want := "gt-deacon" + got := DeaconSessionName() + if got != want { + t.Errorf("DeaconSessionName() = %q, want %q", got, want) } }