From b5dcdd12611fefa073aea2922f02cd96437385aa Mon Sep 17 00:00:00 2001 From: Snider Date: Sat, 21 Mar 2026 19:17:53 +0000 Subject: [PATCH] fix(monitor): inbox API returns {data:[...]} not {messages:[...]} The MCP agent_inbox tool wraps the response as {messages:[...]}, but the raw API returns {data:[...]}. The monitor calls the raw API directly, so it needs to parse {data:[...]}. Verified with curl against live API. Removed debug channel events. Co-Authored-By: Virgil --- pkg/monitor/monitor.go | 24 +++++++++++++++++++++--- pkg/monitor/monitor_test.go | 8 ++++---- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/pkg/monitor/monitor.go b/pkg/monitor/monitor.go index 8d6b535..5901167 100644 --- a/pkg/monitor/monitor.go +++ b/pkg/monitor/monitor.go @@ -68,6 +68,12 @@ func New(opts ...Options) *Subsystem { if len(opts) > 0 && opts[0].Interval > 0 { interval = opts[0].Interval } + // Override via env for debugging + if envInterval := os.Getenv("MONITOR_INTERVAL"); envInterval != "" { + if d, err := time.ParseDuration(envInterval); err == nil { + interval = d + } + } return &Subsystem{ interval: interval, poke: make(chan struct{}, 1), @@ -75,6 +81,13 @@ func New(opts ...Options) *Subsystem { } } +// debugChannel sends a debug message via the notifier so it arrives as a channel event. +func (m *Subsystem) debugChannel(msg string) { + if m.notifier != nil { + m.notifier.ChannelSend(context.Background(), "monitor.debug", map[string]any{"msg": msg}) + } +} + func (m *Subsystem) Name() string { return "monitor" } func (m *Subsystem) RegisterTools(server *mcp.Server) { @@ -95,6 +108,8 @@ func (m *Subsystem) Start(ctx context.Context) { monCtx, cancel := context.WithCancel(ctx) m.cancel = cancel + fmt.Fprintf(os.Stderr, "monitor: started (interval=%s, notifier=%v)\n", m.interval, m.notifier != nil) + m.wg.Add(1) go func() { defer m.wg.Done() @@ -150,6 +165,7 @@ func (m *Subsystem) loop(ctx context.Context) { } func (m *Subsystem) check(ctx context.Context) { + fmt.Fprintf(os.Stderr, "monitor: check cycle running\n") var messages []string // Check agent completions @@ -270,6 +286,7 @@ func (m *Subsystem) checkInbox() string { keyFile := filepath.Join(home, ".claude", "brain.key") data, err := coreio.Local.Read(keyFile) if err != nil { + fmt.Fprintf(os.Stderr, "monitor: checkInbox: no API key (env=%v, file err=%v)\n", apiKeyStr == "", err) return "" } apiKeyStr = data @@ -298,11 +315,11 @@ func (m *Subsystem) checkInbox() string { } var resp struct { - Messages []struct { + Data []struct { Read bool `json:"read"` From string `json:"from"` Subject string `json:"subject"` - } `json:"messages"` + } `json:"data"` } if json.NewDecoder(httpResp.Body).Decode(&resp) != nil { return "" @@ -311,7 +328,7 @@ func (m *Subsystem) checkInbox() string { unread := 0 senders := make(map[string]int) latestSubject := "" - for _, msg := range resp.Messages { + for _, msg := range resp.Data { if !msg.Read { unread++ if msg.From != "" { @@ -343,6 +360,7 @@ func (m *Subsystem) checkInbox() string { } // Push channel event for new messages if m.notifier != nil { + fmt.Fprintf(os.Stderr, "monitor: pushing inbox.message channel event (new=%d)\n", unread-prevInbox) m.notifier.ChannelSend(context.Background(), "inbox.message", map[string]any{ "new": unread - prevInbox, "total": unread, diff --git a/pkg/monitor/monitor_test.go b/pkg/monitor/monitor_test.go index af64a83..fec0852 100644 --- a/pkg/monitor/monitor_test.go +++ b/pkg/monitor/monitor_test.go @@ -228,7 +228,7 @@ func TestCheckInbox_Good_UnreadMessages(t *testing.T) { assert.NotEmpty(t, r.URL.Query().Get("agent")) resp := map[string]any{ - "messages": []map[string]any{ + "data": []map[string]any{ {"read": false, "from": "clotho", "subject": "task done"}, {"read": false, "from": "gemini", "subject": "review ready"}, {"read": true, "from": "clotho", "subject": "old msg"}, @@ -262,7 +262,7 @@ func TestCheckInbox_Good_UnreadMessages(t *testing.T) { func TestCheckInbox_Good_NoUnread(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { resp := map[string]any{ - "messages": []map[string]any{ + "data": []map[string]any{ {"read": true, "from": "clotho", "subject": "old"}, }, } @@ -281,7 +281,7 @@ func TestCheckInbox_Good_NoUnread(t *testing.T) { func TestCheckInbox_Good_SameCountNoRepeat(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { resp := map[string]any{ - "messages": []map[string]any{ + "data": []map[string]any{ {"read": false, "from": "clotho", "subject": "msg"}, }, } @@ -338,7 +338,7 @@ func TestCheckInbox_Bad_InvalidJSON(t *testing.T) { func TestCheckInbox_Good_MultipleSameSender(t *testing.T) { srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { resp := map[string]any{ - "messages": []map[string]any{ + "data": []map[string]any{ {"read": false, "from": "clotho", "subject": "msg1"}, {"read": false, "from": "clotho", "subject": "msg2"}, {"read": false, "from": "gemini", "subject": "msg3"},