diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index 3d13071c01..d25afdadc1 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -336,15 +336,17 @@ func (al *AgentLoop) Run(ctx context.Context) error { } if response != "" { - // Check if the message tool already sent a response during this round. + // Check if the message tool already sent a response to the SAME chat during this round. // If so, skip publishing to avoid duplicate messages to the user. + // Only skip when the target chat_id matches — sending to a different chat + // (e.g. a newly created group) should not suppress the reply to the original chat. // Use default agent's tools to check (message tool is shared). alreadySent := false defaultAgent := al.registry.GetDefaultAgent() if defaultAgent != nil { if tool, ok := defaultAgent.Tools.Get("message"); ok { if mt, ok := tool.(*tools.MessageTool); ok { - alreadySent = mt.HasSentInRound() + alreadySent = mt.HasSentInRound() && mt.HasSentToChatID(msg.ChatID) } } } diff --git a/pkg/tools/message.go b/pkg/tools/message.go index 438ceeddd4..854309df05 100644 --- a/pkg/tools/message.go +++ b/pkg/tools/message.go @@ -3,6 +3,7 @@ package tools import ( "context" "fmt" + "sync" "sync/atomic" ) @@ -11,6 +12,9 @@ type SendCallback func(channel, chatID, content string) error type MessageTool struct { sendCallback SendCallback sentInRound atomic.Bool // Tracks whether a message was sent in the current processing round + + mu sync.Mutex // protects sentChatIDs + sentChatIDs map[string]struct{} // All chat_ids the message tool sent to in the current round } func NewMessageTool() *MessageTool { @@ -50,6 +54,9 @@ func (t *MessageTool) Parameters() map[string]any { // Called by the agent loop at the start of each inbound message processing round. func (t *MessageTool) ResetSentInRound() { t.sentInRound.Store(false) + t.mu.Lock() + t.sentChatIDs = nil + t.mu.Unlock() } // HasSentInRound returns true if the message tool sent a message during the current round. @@ -57,6 +64,14 @@ func (t *MessageTool) HasSentInRound() bool { return t.sentInRound.Load() } +// HasSentToChatID returns true if the message tool sent to the given chat_id in this round. +func (t *MessageTool) HasSentToChatID(chatID string) bool { + t.mu.Lock() + defer t.mu.Unlock() + _, ok := t.sentChatIDs[chatID] + return ok +} + func (t *MessageTool) SetSendCallback(callback SendCallback) { t.sendCallback = callback } @@ -94,6 +109,12 @@ func (t *MessageTool) Execute(ctx context.Context, args map[string]any) *ToolRes } t.sentInRound.Store(true) + t.mu.Lock() + if t.sentChatIDs == nil { + t.sentChatIDs = make(map[string]struct{}) + } + t.sentChatIDs[chatID] = struct{}{} + t.mu.Unlock() // Silent: user already received the message directly return &ToolResult{ ForLLM: fmt.Sprintf("Message sent to %s:%s", channel, chatID),