fix(handoff): normalize identity in sendHandoffMail (#780)

The handoff mail bead was created with un-normalized assignee
(e.g., gastown/crew/holden) but mailbox queries use normalized identity
(gastown/holden), causing self-mail to be invisible to the inbox.

Export addressToIdentity as AddressToIdentity and call it in
sendHandoffMail() to normalize the agent identity before storing,
matching the normalization performed in Router.sendToSingle().

Fix handoff mail delivery (hq-snp8)
This commit is contained in:
aleiby
2026-01-20 14:09:54 -08:00
committed by GitHub
parent 2fe23b7be5
commit 08cee416a4
5 changed files with 15 additions and 11 deletions

View File

@@ -11,6 +11,7 @@ import (
"github.com/steveyegge/gastown/internal/config" "github.com/steveyegge/gastown/internal/config"
"github.com/steveyegge/gastown/internal/constants" "github.com/steveyegge/gastown/internal/constants"
"github.com/steveyegge/gastown/internal/events" "github.com/steveyegge/gastown/internal/events"
"github.com/steveyegge/gastown/internal/mail"
"github.com/steveyegge/gastown/internal/session" "github.com/steveyegge/gastown/internal/session"
"github.com/steveyegge/gastown/internal/style" "github.com/steveyegge/gastown/internal/style"
"github.com/steveyegge/gastown/internal/tmux" "github.com/steveyegge/gastown/internal/tmux"
@@ -577,6 +578,9 @@ func sendHandoffMail(subject, message string) (string, error) {
return "", fmt.Errorf("detecting agent identity: %w", err) return "", fmt.Errorf("detecting agent identity: %w", err)
} }
// Normalize identity to match mailbox query format
agentID = mail.AddressToIdentity(agentID)
// Detect town root for beads location // Detect town root for beads location
townRoot := detectTownRootFromCwd() townRoot := detectTownRootFromCwd()
if townRoot == "" { if townRoot == "" {

View File

@@ -56,7 +56,7 @@ func NewMailboxBeads(identity, workDir string) *Mailbox {
func NewMailboxFromAddress(address, workDir string) *Mailbox { func NewMailboxFromAddress(address, workDir string) *Mailbox {
beadsDir := beads.ResolveBeadsDir(workDir) beadsDir := beads.ResolveBeadsDir(workDir)
return &Mailbox{ return &Mailbox{
identity: addressToIdentity(address), identity: AddressToIdentity(address),
workDir: workDir, workDir: workDir,
beadsDir: beadsDir, beadsDir: beadsDir,
legacy: false, legacy: false,
@@ -66,7 +66,7 @@ func NewMailboxFromAddress(address, workDir string) *Mailbox {
// NewMailboxWithBeadsDir creates a mailbox with an explicit beads directory. // NewMailboxWithBeadsDir creates a mailbox with an explicit beads directory.
func NewMailboxWithBeadsDir(address, workDir, beadsDir string) *Mailbox { func NewMailboxWithBeadsDir(address, workDir, beadsDir string) *Mailbox {
return &Mailbox{ return &Mailbox{
identity: addressToIdentity(address), identity: AddressToIdentity(address),
workDir: workDir, workDir: workDir,
beadsDir: beadsDir, beadsDir: beadsDir,
legacy: false, legacy: false,

View File

@@ -569,7 +569,7 @@ func (r *Router) sendToGroup(msg *Message) error {
// sendToSingle sends a message to a single recipient. // sendToSingle sends a message to a single recipient.
func (r *Router) sendToSingle(msg *Message) error { func (r *Router) sendToSingle(msg *Message) error {
// Convert addresses to beads identities // Convert addresses to beads identities
toIdentity := addressToIdentity(msg.To) toIdentity := AddressToIdentity(msg.To)
// Build labels for from/thread/reply-to/cc // Build labels for from/thread/reply-to/cc
var labels []string var labels []string
@@ -582,7 +582,7 @@ func (r *Router) sendToSingle(msg *Message) error {
} }
// Add CC labels (one per recipient) // Add CC labels (one per recipient)
for _, cc := range msg.CC { for _, cc := range msg.CC {
ccIdentity := addressToIdentity(cc) ccIdentity := AddressToIdentity(cc)
labels = append(labels, "cc:"+ccIdentity) labels = append(labels, "cc:"+ccIdentity)
} }
@@ -692,7 +692,7 @@ func (r *Router) sendToQueue(msg *Message) error {
labels = append(labels, "reply-to:"+msg.ReplyTo) labels = append(labels, "reply-to:"+msg.ReplyTo)
} }
for _, cc := range msg.CC { for _, cc := range msg.CC {
ccIdentity := addressToIdentity(cc) ccIdentity := AddressToIdentity(cc)
labels = append(labels, "cc:"+ccIdentity) labels = append(labels, "cc:"+ccIdentity)
} }
@@ -763,7 +763,7 @@ func (r *Router) sendToAnnounce(msg *Message) error {
labels = append(labels, "reply-to:"+msg.ReplyTo) labels = append(labels, "reply-to:"+msg.ReplyTo)
} }
for _, cc := range msg.CC { for _, cc := range msg.CC {
ccIdentity := addressToIdentity(cc) ccIdentity := AddressToIdentity(cc)
labels = append(labels, "cc:"+ccIdentity) labels = append(labels, "cc:"+ccIdentity)
} }
@@ -836,7 +836,7 @@ func (r *Router) sendToChannel(msg *Message) error {
labels = append(labels, "reply-to:"+msg.ReplyTo) labels = append(labels, "reply-to:"+msg.ReplyTo)
} }
for _, cc := range msg.CC { for _, cc := range msg.CC {
ccIdentity := addressToIdentity(cc) ccIdentity := AddressToIdentity(cc)
labels = append(labels, "cc:"+ccIdentity) labels = append(labels, "cc:"+ccIdentity)
} }

View File

@@ -488,7 +488,7 @@ func ParseMessageType(s string) MessageType {
} }
} }
// addressToIdentity converts a GGT address to a beads identity. // AddressToIdentity converts a GGT address to a beads identity.
// //
// Liberal normalization: accepts multiple address formats and normalizes // Liberal normalization: accepts multiple address formats and normalizes
// to canonical form (Postel's Law - be liberal in what you accept). // to canonical form (Postel's Law - be liberal in what you accept).
@@ -504,7 +504,7 @@ func ParseMessageType(s string) MessageType {
// - "gastown/Toast" → "gastown/Toast" (already canonical) // - "gastown/Toast" → "gastown/Toast" (already canonical)
// - "gastown/refinery" → "gastown/refinery" // - "gastown/refinery" → "gastown/refinery"
// - "gastown/" → "gastown" (rig broadcast) // - "gastown/" → "gastown" (rig broadcast)
func addressToIdentity(address string) string { func AddressToIdentity(address string) string {
// Overseer (human operator) - no trailing slash, distinct from agents // Overseer (human operator) - no trailing slash, distinct from agents
if address == "overseer" { if address == "overseer" {
return "overseer" return "overseer"

View File

@@ -30,9 +30,9 @@ func TestAddressToIdentity(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.address, func(t *testing.T) { t.Run(tt.address, func(t *testing.T) {
got := addressToIdentity(tt.address) got := AddressToIdentity(tt.address)
if got != tt.expected { if got != tt.expected {
t.Errorf("addressToIdentity(%q) = %q, want %q", tt.address, got, tt.expected) t.Errorf("AddressToIdentity(%q) = %q, want %q", tt.address, got, tt.expected)
} }
}) })
} }