feat: Add overseer identity for human operator mail support

Adds first-class support for the human overseer in Gas Town mail:

- New OverseerConfig in internal/config/overseer.go with identity
  detection (git config, gh cli, environment)
- Overseer detected/saved on town install (mayor/overseer.json)
- Simplified detectSender(): GT_ROLE set = agent, else = overseer
- New overseer address alongside mayor/ and deacon/
- Added --cc flag to mail send for CC recipients
- Inbox now includes CC'd messages via label query
- gt status shows overseer identity and unread mail count
- New gt whoami command shows current mail identity

Generated with Claude Code

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-29 18:02:49 -08:00
parent cab9e10d30
commit a09027043d
8 changed files with 550 additions and 22 deletions

View File

@@ -40,11 +40,21 @@ func init() {
// TownStatus represents the overall status of the workspace.
type TownStatus struct {
Name string `json:"name"`
Location string `json:"location"`
Agents []AgentRuntime `json:"agents"` // Global agents (Mayor, Deacon)
Rigs []RigStatus `json:"rigs"`
Summary StatusSum `json:"summary"`
Name string `json:"name"`
Location string `json:"location"`
Overseer *OverseerInfo `json:"overseer,omitempty"` // Human operator
Agents []AgentRuntime `json:"agents"` // Global agents (Mayor, Deacon)
Rigs []RigStatus `json:"rigs"`
Summary StatusSum `json:"summary"`
}
// OverseerInfo represents the human operator's identity and status.
type OverseerInfo struct {
Name string `json:"name"`
Email string `json:"email,omitempty"`
Username string `json:"username,omitempty"`
Source string `json:"source"`
UnreadMail int `json:"unread_mail"`
}
// AgentRuntime represents the runtime state of an agent.
@@ -137,10 +147,27 @@ func runStatus(cmd *cobra.Command, args []string) error {
// Create mail router for inbox lookups
mailRouter := mail.NewRouter(townRoot)
// Load overseer config
var overseerInfo *OverseerInfo
if overseerConfig, err := config.LoadOrDetectOverseer(townRoot); err == nil && overseerConfig != nil {
overseerInfo = &OverseerInfo{
Name: overseerConfig.Name,
Email: overseerConfig.Email,
Username: overseerConfig.Username,
Source: overseerConfig.Source,
}
// Get overseer mail count
if mailbox, err := mailRouter.GetMailbox("overseer"); err == nil {
_, unread, _ := mailbox.Count()
overseerInfo.UnreadMail = unread
}
}
// Build status
status := TownStatus{
Name: townConfig.Name,
Location: townRoot,
Overseer: overseerInfo,
Agents: discoverGlobalAgents(t, agentBeads, mailRouter),
Rigs: make([]RigStatus, 0, len(rigs)),
}
@@ -207,6 +234,21 @@ func outputStatusText(status TownStatus) error {
fmt.Printf("%s %s\n", style.Bold.Render("Town:"), status.Name)
fmt.Printf("%s\n\n", style.Dim.Render(status.Location))
// Overseer info
if status.Overseer != nil {
overseerDisplay := status.Overseer.Name
if status.Overseer.Email != "" {
overseerDisplay = fmt.Sprintf("%s <%s>", status.Overseer.Name, status.Overseer.Email)
} else if status.Overseer.Username != "" && status.Overseer.Username != status.Overseer.Name {
overseerDisplay = fmt.Sprintf("%s (@%s)", status.Overseer.Name, status.Overseer.Username)
}
fmt.Printf("👤 %s %s\n", style.Bold.Render("Overseer:"), overseerDisplay)
if status.Overseer.UnreadMail > 0 {
fmt.Printf(" 📬 %d unread\n", status.Overseer.UnreadMail)
}
fmt.Println()
}
// Role icons
roleIcons := map[string]string{
"mayor": "🎩",