feat: gt mail send now sends tmux notification to recipients
- Add DisplayMessage/DisplayMessageDefault to tmux package for non-disruptive status line notifications - Change mail send to always notify recipients (not just high priority) - Use display-message instead of send-keys to avoid disrupting agent input - Support notifications for mayor, polecat, and refinery sessions Closes gt-7lt 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -56,10 +56,8 @@ func (r *Router) Send(msg *Message) error {
|
|||||||
return fmt.Errorf("sending message: %w", err)
|
return fmt.Errorf("sending message: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optionally notify if recipient is a polecat with active session
|
// Always notify recipient if they have an active session
|
||||||
if isPolecat(msg.To) && msg.Priority == PriorityHigh {
|
r.notifyRecipient(msg)
|
||||||
r.notifyPolecat(msg)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@@ -69,43 +67,44 @@ func (r *Router) GetMailbox(address string) (*Mailbox, error) {
|
|||||||
return NewMailboxFromAddress(address, r.workDir), nil
|
return NewMailboxFromAddress(address, r.workDir), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// notifyPolecat sends a notification to a polecat's tmux session.
|
// notifyRecipient sends a notification to a recipient's tmux session.
|
||||||
func (r *Router) notifyPolecat(msg *Message) error {
|
// Uses display-message for non-disruptive notification.
|
||||||
// Parse rig/polecat from address
|
// Supports mayor/, rig/polecat, and rig/refinery addresses.
|
||||||
parts := strings.SplitN(msg.To, "/", 2)
|
func (r *Router) notifyRecipient(msg *Message) error {
|
||||||
if len(parts) != 2 {
|
sessionID := addressToSessionID(msg.To)
|
||||||
return nil
|
if sessionID == "" {
|
||||||
|
return nil // Unable to determine session ID
|
||||||
}
|
}
|
||||||
|
|
||||||
rig := parts[0]
|
|
||||||
polecat := parts[1]
|
|
||||||
|
|
||||||
// Generate session name (matches session.Manager)
|
|
||||||
sessionID := fmt.Sprintf("gt-%s-%s", rig, polecat)
|
|
||||||
|
|
||||||
// Check if session exists
|
// Check if session exists
|
||||||
hasSession, err := r.tmux.HasSession(sessionID)
|
hasSession, err := r.tmux.HasSession(sessionID)
|
||||||
if err != nil || !hasSession {
|
if err != nil || !hasSession {
|
||||||
return nil // No active session, skip notification
|
return nil // No active session, skip notification
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inject notification
|
// Display notification in status line (non-disruptive)
|
||||||
notification := fmt.Sprintf("[MAIL] %s", msg.Subject)
|
notification := fmt.Sprintf("[MAIL] From %s: %s", msg.From, msg.Subject)
|
||||||
return r.tmux.SendKeys(sessionID, notification)
|
return r.tmux.DisplayMessageDefault(sessionID, notification)
|
||||||
}
|
}
|
||||||
|
|
||||||
// isPolecat checks if an address points to a polecat.
|
// addressToSessionID converts a mail address to a tmux session ID.
|
||||||
func isPolecat(address string) bool {
|
// Returns empty string if address format is not recognized.
|
||||||
// Not mayor, not refinery, has rig/name format
|
func addressToSessionID(address string) string {
|
||||||
|
// Mayor address: "mayor/" or "mayor"
|
||||||
if strings.HasPrefix(address, "mayor") {
|
if strings.HasPrefix(address, "mayor") {
|
||||||
return false
|
return "gt-mayor"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Rig-based address: "rig/target"
|
||||||
parts := strings.SplitN(address, "/", 2)
|
parts := strings.SplitN(address, "/", 2)
|
||||||
if len(parts) != 2 {
|
if len(parts) != 2 || parts[1] == "" {
|
||||||
return false
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rig := parts[0]
|
||||||
target := parts[1]
|
target := parts[1]
|
||||||
return target != "" && target != "refinery"
|
|
||||||
|
// Polecat: gt-rig-polecat
|
||||||
|
// Refinery: gt-rig-refinery (if refinery has its own session)
|
||||||
|
return fmt.Sprintf("gt-%s-%s", rig, target)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -200,6 +200,21 @@ type SessionInfo struct {
|
|||||||
Attached bool
|
Attached bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DisplayMessage shows a message in the tmux status line.
|
||||||
|
// This is non-disruptive - it doesn't interrupt the session's input.
|
||||||
|
// Duration is specified in milliseconds.
|
||||||
|
func (t *Tmux) DisplayMessage(session, message string, durationMs int) error {
|
||||||
|
// Set display time temporarily, show message, then restore
|
||||||
|
// Use -d flag for duration in tmux 2.9+
|
||||||
|
_, err := t.run("display-message", "-t", session, "-d", fmt.Sprintf("%d", durationMs), message)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisplayMessageDefault shows a message with default duration (5 seconds).
|
||||||
|
func (t *Tmux) DisplayMessageDefault(session, message string) error {
|
||||||
|
return t.DisplayMessage(session, message, 5000)
|
||||||
|
}
|
||||||
|
|
||||||
// GetSessionInfo returns detailed information about a session.
|
// GetSessionInfo returns detailed information about a session.
|
||||||
func (t *Tmux) GetSessionInfo(name string) (*SessionInfo, error) {
|
func (t *Tmux) GetSessionInfo(name string) (*SessionInfo, error) {
|
||||||
format := "#{session_name}|#{session_windows}|#{session_created_string}|#{session_attached}"
|
format := "#{session_name}|#{session_windows}|#{session_created_string}|#{session_attached}"
|
||||||
|
|||||||
Reference in New Issue
Block a user