From 12eb3a7e9ffa1b79b033ffc2e3230a570ed38008 Mon Sep 17 00:00:00 2001 From: merlinmiao <820962493@qq.com> Date: Sun, 5 Apr 2026 18:34:48 +0800 Subject: [PATCH] fix(agent): route cron job messages to originating chat session When a cron job fires, ProcessDirectWithChannel creates an InboundMessage with Peer.Kind empty. extractPeer returned nil in this case, causing resolveMessageRoute to fall through to the default route (agent:main:main) instead of routing to the originating Telegram/DingTalk chat session. This change makes extractPeer treat empty Peer.Kind with a non-empty ChatID as a direct message (Kind="direct", ID=ChatID), ensuring the cron job response is routed to the correct chat session. Fixes #2275 Co-Authored-By: Claude Opus 4.6 --- pkg/agent/loop.go | 15 +++++-- pkg/agent/loop_test.go | 95 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 4 deletions(-) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index fc37ff8a0..e3052c836 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -3362,18 +3362,25 @@ func mapCommandError(result commands.ExecuteResult) string { // extractPeer extracts the routing peer from the inbound message's structured Peer field. func extractPeer(msg bus.InboundMessage) *routing.RoutePeer { - if msg.Peer.Kind == "" { + peerKind := msg.Peer.Kind + peerID := msg.Peer.ID + // When Peer.Kind is empty but ChatID is set (e.g., cron jobs), treat as a + // direct message so session routing resolves to the correct chat session. + if peerKind == "" && msg.ChatID != "" { + peerKind = "direct" + peerID = msg.ChatID + } + if peerKind == "" { return nil } - peerID := msg.Peer.ID if peerID == "" { - if msg.Peer.Kind == "direct" { + if peerKind == "direct" { peerID = msg.SenderID } else { peerID = msg.ChatID } } - return &routing.RoutePeer{Kind: msg.Peer.Kind, ID: peerID} + return &routing.RoutePeer{Kind: peerKind, ID: peerID} } func inboundMetadata(msg bus.InboundMessage, key string) string { diff --git a/pkg/agent/loop_test.go b/pkg/agent/loop_test.go index 9513d8aca..089c57dd9 100644 --- a/pkg/agent/loop_test.go +++ b/pkg/agent/loop_test.go @@ -3046,3 +3046,98 @@ func TestProcessMessage_ContextOverflow_AnthropicStyle(t *testing.T) { t.Fatalf("expected 2 calls for retry, got %d", provider.calls) } } + +func TestExtractPeer_CronJobWithEmptyPeerKind(t *testing.T) { + // When Peer.Kind is empty but ChatID is set (e.g., cron jobs), + // extractPeer should treat it as a direct message using ChatID. + // This ensures cron job responses route to the correct chat session. + msg := bus.InboundMessage{ + Channel: "telegram", + SenderID: "cron", + ChatID: "439850467", + Peer: bus.Peer{}, // Kind is empty + } + got := extractPeer(msg) + if got == nil { + t.Fatal("extractPeer returned nil, want Peer with Kind=direct") + } + if got.Kind != "direct" { + t.Errorf("got.Kind = %q, want %q", got.Kind, "direct") + } + if got.ID != "439850467" { + t.Errorf("got.ID = %q, want %q", got.ID, "439850467") + } +} + +func TestExtractPeer_EmptyPeerKindAndEmptyChatID(t *testing.T) { + // When both Peer.Kind and ChatID are empty, extractPeer should return nil. + msg := bus.InboundMessage{ + Channel: "telegram", + SenderID: "cron", + Peer: bus.Peer{}, + } + got := extractPeer(msg) + if got != nil { + t.Errorf("extractPeer returned %+v, want nil", got) + } +} + +func TestExtractPeer_NormalDirectMessage(t *testing.T) { + // Normal direct message with Peer.Kind and Peer.ID set. + msg := bus.InboundMessage{ + Channel: "telegram", + SenderID: "user1", + ChatID: "chat1", + Peer: bus.Peer{ + Kind: "direct", + ID: "user1", + }, + } + got := extractPeer(msg) + if got == nil { + t.Fatal("extractPeer returned nil") + } + if got.Kind != "direct" || got.ID != "user1" { + t.Errorf("extractPeer = %+v, want Kind=direct, ID=user1", got) + } +} + +func TestExtractPeer_DirectMessageWithEmptyPeerID(t *testing.T) { + // When Peer.Kind is "direct" but Peer.ID is empty, should use SenderID. + msg := bus.InboundMessage{ + Channel: "telegram", + SenderID: "user1", + ChatID: "chat1", + Peer: bus.Peer{ + Kind: "direct", + ID: "", + }, + } + got := extractPeer(msg) + if got == nil { + t.Fatal("extractPeer returned nil") + } + if got.ID != "user1" { + t.Errorf("got.ID = %q, want %q", got.ID, "user1") + } +} + +func TestExtractPeer_GroupMessageWithEmptyPeerID(t *testing.T) { + // When Peer.Kind is "group" but Peer.ID is empty, should use ChatID. + msg := bus.InboundMessage{ + Channel: "telegram", + SenderID: "user1", + ChatID: "group_chat_123", + Peer: bus.Peer{ + Kind: "group", + ID: "", + }, + } + got := extractPeer(msg) + if got == nil { + t.Fatal("extractPeer returned nil") + } + if got.ID != "group_chat_123" { + t.Errorf("got.ID = %q, want %q", got.ID, "group_chat_123") + } +}