feat: refactor mail system to use bd mail backend
- Mail commands (send/inbox/read/delete) now wrap bd mail CLI - Address translation: mayor/ → mayor, rig/polecat → rig-polecat - Beads stores messages as type=message issues - Legacy JSONL mode retained for crew workers (local mail) - Refinery notifications use new mail interface - Swarm landing notifications use new mail interface Closes gt-u1j.6, gt-u1j.12 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
// Package mail provides JSONL-based messaging for agent communication.
|
||||
// Package mail provides messaging for agent communication via beads.
|
||||
package mail
|
||||
|
||||
import (
|
||||
@@ -19,8 +19,9 @@ const (
|
||||
)
|
||||
|
||||
// Message represents a mail message between agents.
|
||||
// This is the GGT-side representation; it gets translated to/from beads messages.
|
||||
type Message struct {
|
||||
// ID is a unique message identifier.
|
||||
// ID is a unique message identifier (beads issue ID like "bd-abc123").
|
||||
ID string `json:"id"`
|
||||
|
||||
// From is the sender address (e.g., "gastown/Toast" or "mayor/").
|
||||
@@ -38,14 +39,14 @@ type Message struct {
|
||||
// Timestamp is when the message was sent.
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
|
||||
// Read indicates if the message has been read.
|
||||
// Read indicates if the message has been read (closed in beads).
|
||||
Read bool `json:"read"`
|
||||
|
||||
// Priority is the message priority.
|
||||
Priority Priority `json:"priority"`
|
||||
}
|
||||
|
||||
// NewMessage creates a new message with a generated ID.
|
||||
// NewMessage creates a new message with a generated ID (for legacy JSONL mode).
|
||||
func NewMessage(from, to, subject, body string) *Message {
|
||||
return &Message{
|
||||
ID: generateID(),
|
||||
@@ -65,3 +66,88 @@ func generateID() string {
|
||||
rand.Read(b)
|
||||
return "msg-" + hex.EncodeToString(b)
|
||||
}
|
||||
|
||||
// BeadsMessage represents a message as returned by bd mail commands.
|
||||
type BeadsMessage struct {
|
||||
ID string `json:"id"`
|
||||
Title string `json:"title"` // Subject
|
||||
Description string `json:"description"` // Body
|
||||
Sender string `json:"sender"` // From identity
|
||||
Assignee string `json:"assignee"` // To identity
|
||||
Priority int `json:"priority"` // 0=urgent, 2=normal
|
||||
Status string `json:"status"` // open=unread, closed=read
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// ToMessage converts a BeadsMessage to a GGT Message.
|
||||
func (bm *BeadsMessage) ToMessage() *Message {
|
||||
priority := PriorityNormal
|
||||
if bm.Priority == 0 {
|
||||
priority = PriorityHigh
|
||||
}
|
||||
|
||||
return &Message{
|
||||
ID: bm.ID,
|
||||
From: identityToAddress(bm.Sender),
|
||||
To: identityToAddress(bm.Assignee),
|
||||
Subject: bm.Title,
|
||||
Body: bm.Description,
|
||||
Timestamp: bm.CreatedAt,
|
||||
Read: bm.Status == "closed",
|
||||
Priority: priority,
|
||||
}
|
||||
}
|
||||
|
||||
// addressToIdentity converts a GGT address to a beads identity.
|
||||
//
|
||||
// Examples:
|
||||
// - "mayor/" → "mayor"
|
||||
// - "gastown/Toast" → "gastown-Toast"
|
||||
// - "gastown/refinery" → "gastown-refinery"
|
||||
// - "gastown/" → "gastown" (rig broadcast)
|
||||
func addressToIdentity(address string) string {
|
||||
// Trim trailing slash
|
||||
if len(address) > 0 && address[len(address)-1] == '/' {
|
||||
address = address[:len(address)-1]
|
||||
}
|
||||
|
||||
// Mayor special case
|
||||
if address == "mayor" {
|
||||
return "mayor"
|
||||
}
|
||||
|
||||
// Replace / with - for beads identity
|
||||
// gastown/Toast → gastown-Toast
|
||||
result := ""
|
||||
for _, c := range address {
|
||||
if c == '/' {
|
||||
result += "-"
|
||||
} else {
|
||||
result = result + string(c)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// identityToAddress converts a beads identity back to a GGT address.
|
||||
//
|
||||
// Examples:
|
||||
// - "mayor" → "mayor/"
|
||||
// - "gastown-Toast" → "gastown/Toast"
|
||||
// - "gastown-refinery" → "gastown/refinery"
|
||||
func identityToAddress(identity string) string {
|
||||
if identity == "mayor" {
|
||||
return "mayor/"
|
||||
}
|
||||
|
||||
// Find first dash and replace with /
|
||||
// gastown-Toast → gastown/Toast
|
||||
for i, c := range identity {
|
||||
if c == '-' {
|
||||
return identity[:i] + "/" + identity[i+1:]
|
||||
}
|
||||
}
|
||||
|
||||
// No dash found, return as-is with trailing slash
|
||||
return identity + "/"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user