feat: implement rig/town-level mail routing and shared beads for polecats

This commit is contained in:
Steve Yegge
2025-12-20 21:04:23 -08:00
parent fc1f50502f
commit b9f93fc806
3 changed files with 172 additions and 108 deletions

View File

@@ -22,6 +22,7 @@ var (
type Mailbox struct {
identity string // beads identity (e.g., "gastown-Toast")
workDir string // directory to run bd commands in
beadsDir string // explicit .beads directory path (set via BEADS_DIR)
path string // for legacy JSONL mode (crew workers)
legacy bool // true = use JSONL files, false = use beads
}
@@ -45,12 +46,22 @@ func NewMailboxBeads(identity, workDir string) *Mailbox {
}
// NewMailboxFromAddress creates a beads-backed mailbox from a GGT address.
// The address is stored as-is (not converted to identity) to match how
// messages are stored with their assignee field.
func NewMailboxFromAddress(address, workDir string) *Mailbox {
beadsDir := filepath.Join(workDir, ".beads")
return &Mailbox{
identity: address, // Use address directly, not identity format
identity: addressToIdentity(address),
workDir: workDir,
beadsDir: beadsDir,
legacy: false,
}
}
// NewMailboxWithBeadsDir creates a mailbox with an explicit beads directory.
func NewMailboxWithBeadsDir(address, workDir, beadsDir string) *Mailbox {
return &Mailbox{
identity: addressToIdentity(address),
workDir: workDir,
beadsDir: beadsDir,
legacy: false,
}
}
@@ -74,13 +85,13 @@ func (m *Mailbox) List() ([]*Message, error) {
}
func (m *Mailbox) listBeads() ([]*Message, error) {
// bd list --type=message --assignee=<identity> --status=open --json
cmd := exec.Command("bd", "list",
"--type", "message",
"--assignee", m.identity,
"--status", "open",
"--json")
// bd mail inbox --json
cmd := exec.Command("bd", "mail", "inbox", "--json")
cmd.Dir = m.workDir
cmd.Env = append(cmd.Environ(),
"BD_IDENTITY="+m.identity,
"BEADS_DIR="+m.beadsDir,
)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
@@ -104,14 +115,6 @@ func (m *Mailbox) listBeads() ([]*Message, error) {
return nil, err
}
// Sort pinned messages first, then by timestamp (newest first)
sort.Slice(beadsMsgs, func(i, j int) bool {
if beadsMsgs[i].Pinned != beadsMsgs[j].Pinned {
return beadsMsgs[i].Pinned // pinned comes first
}
return beadsMsgs[i].CreatedAt.After(beadsMsgs[j].CreatedAt)
})
// Convert to GGT messages
var messages []*Message
for _, bm := range beadsMsgs {
@@ -186,9 +189,9 @@ func (m *Mailbox) Get(id string) (*Message, error) {
}
func (m *Mailbox) getBeads(id string) (*Message, error) {
// bd show <id> --json returns an array with one element
cmd := exec.Command("bd", "show", id, "--json")
cmd := exec.Command("bd", "mail", "read", id, "--json")
cmd.Dir = m.workDir
cmd.Env = append(cmd.Environ(), "BEADS_DIR="+m.beadsDir)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
@@ -205,16 +208,12 @@ func (m *Mailbox) getBeads(id string) (*Message, error) {
return nil, err
}
// bd show returns an array with one element
var beadsMsgs []BeadsMessage
if err := json.Unmarshal(stdout.Bytes(), &beadsMsgs); err != nil {
var bm BeadsMessage
if err := json.Unmarshal(stdout.Bytes(), &bm); err != nil {
return nil, err
}
if len(beadsMsgs) == 0 {
return nil, ErrMessageNotFound
}
return beadsMsgs[0].ToMessage(), nil
return bm.ToMessage(), nil
}
func (m *Mailbox) getLegacy(id string) (*Message, error) {
@@ -239,9 +238,9 @@ func (m *Mailbox) MarkRead(id string) error {
}
func (m *Mailbox) markReadBeads(id string) error {
// bd close <id> marks the message as read
cmd := exec.Command("bd", "close", id, "--reason", "Message read")
cmd := exec.Command("bd", "mail", "ack", id)
cmd.Dir = m.workDir
cmd.Env = append(cmd.Environ(), "BEADS_DIR="+m.beadsDir)
var stderr bytes.Buffer
cmd.Stderr = &stderr
@@ -408,12 +407,13 @@ func (m *Mailbox) ListByThread(threadID string) ([]*Message, error) {
}
func (m *Mailbox) listByThreadBeads(threadID string) ([]*Message, error) {
// bd list --type=message --label=thread:<thread-id> --json
cmd := exec.Command("bd", "list",
"--type", "message",
"--label", "thread:"+threadID,
"--json")
// bd message thread <thread-id> --json
cmd := exec.Command("bd", "message", "thread", threadID, "--json")
cmd.Dir = m.workDir
cmd.Env = append(cmd.Environ(),
"BD_IDENTITY="+m.identity,
"BEADS_DIR="+m.beadsDir,
)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout