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

@@ -104,11 +104,45 @@ func (m *Mailbox) listBeads() ([]*Message, error) {
}
// listFromDir queries messages from a beads directory.
// Returns messages where identity is the assignee OR a CC recipient.
func (m *Mailbox) listFromDir(beadsDir string) ([]*Message, error) {
// bd list --type=message --assignee=<identity> --json --status=open
// Query 1: messages where identity is the primary recipient
directMsgs, err := m.queryMessages(beadsDir, "--assignee", m.identity)
if err != nil {
return nil, err
}
// Query 2: messages where identity is CC'd
ccMsgs, err := m.queryMessages(beadsDir, "--label", "cc:"+m.identity)
if err != nil {
// CC query failing is non-fatal, just use direct messages
return directMsgs, nil
}
// Merge and dedupe (a message could theoretically be in both if someone CCs the primary recipient)
seen := make(map[string]bool)
var messages []*Message
for _, msg := range directMsgs {
if !seen[msg.ID] {
seen[msg.ID] = true
messages = append(messages, msg)
}
}
for _, msg := range ccMsgs {
if !seen[msg.ID] {
seen[msg.ID] = true
messages = append(messages, msg)
}
}
return messages, nil
}
// queryMessages runs a bd list query with the given filter flag and value.
func (m *Mailbox) queryMessages(beadsDir, filterFlag, filterValue string) ([]*Message, error) {
cmd := exec.Command("bd", "list",
"--type", "message",
"--assignee", m.identity,
filterFlag, filterValue,
"--status", "open",
"--json",
)