refactor(mail): Remove bd mail dependency, use bd create/list/show (gt-9xg)

Replace `bd mail send/inbox/read/ack` commands with `bd create/list/show/close`.
This separates the orchestration layer (gt) from the data plane (beads).

Changes:
- router.go: Use `bd create --type=message` instead of `bd mail send`
- mailbox.go: Use `bd list --type=message` and `bd show` for inbox/read
- types.go: Parse metadata from labels (from:, thread:, reply-to:)
- mail.go: Fix findBeadsWorkDir to prefer rig-level beads, fix crew address format

Messages are now stored as beads issues with type=message. Metadata (sender,
thread, reply-to) is stored in labels for retrieval.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Steve Yegge
2025-12-20 17:52:15 -08:00
parent d242239aa1
commit 4c060f4aaa
5 changed files with 114 additions and 52 deletions

View File

@@ -4,6 +4,7 @@ package mail
import (
"crypto/rand"
"encoding/hex"
"strings"
"time"
)
@@ -142,23 +143,45 @@ func generateThreadID() string {
return "thread-" + hex.EncodeToString(b)
}
// BeadsMessage represents a message as returned by bd mail commands.
// BeadsMessage represents a message as returned by bd list/show commands.
// Messages are beads issues with type=message and metadata stored in labels.
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, 1=high, 2=normal, 3=low
Status string `json:"status"` // open=unread, closed=read
CreatedAt time.Time `json:"created_at"`
Type string `json:"type,omitempty"` // Message type
ThreadID string `json:"thread_id,omitempty"` // Thread identifier
ReplyTo string `json:"reply_to,omitempty"` // Original message ID
Labels []string `json:"labels"` // Metadata labels (from:X, thread:X, reply-to:X, msg-type:X)
// Cached parsed values (populated by ParseLabels)
sender string
threadID string
replyTo string
msgType string
}
// ParseLabels extracts metadata from the labels array.
func (bm *BeadsMessage) ParseLabels() {
for _, label := range bm.Labels {
if strings.HasPrefix(label, "from:") {
bm.sender = strings.TrimPrefix(label, "from:")
} else if strings.HasPrefix(label, "thread:") {
bm.threadID = strings.TrimPrefix(label, "thread:")
} else if strings.HasPrefix(label, "reply-to:") {
bm.replyTo = strings.TrimPrefix(label, "reply-to:")
} else if strings.HasPrefix(label, "msg-type:") {
bm.msgType = strings.TrimPrefix(label, "msg-type:")
}
}
}
// ToMessage converts a BeadsMessage to a GGT Message.
func (bm *BeadsMessage) ToMessage() *Message {
// Parse labels to extract metadata
bm.ParseLabels()
// Convert beads priority (0=urgent, 1=high, 2=normal, 3=low) to GGT Priority
var priority Priority
switch bm.Priority {
@@ -174,14 +197,14 @@ func (bm *BeadsMessage) ToMessage() *Message {
// Convert message type, default to notification
msgType := TypeNotification
switch MessageType(bm.Type) {
switch MessageType(bm.msgType) {
case TypeTask, TypeScavenge, TypeReply:
msgType = MessageType(bm.Type)
msgType = MessageType(bm.msgType)
}
return &Message{
ID: bm.ID,
From: identityToAddress(bm.Sender),
From: identityToAddress(bm.sender),
To: identityToAddress(bm.Assignee),
Subject: bm.Title,
Body: bm.Description,
@@ -189,8 +212,8 @@ func (bm *BeadsMessage) ToMessage() *Message {
Read: bm.Status == "closed",
Priority: priority,
Type: msgType,
ThreadID: bm.ThreadID,
ReplyTo: bm.ReplyTo,
ThreadID: bm.threadID,
ReplyTo: bm.replyTo,
}
}