Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions pkg/agent/loop.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}
}
Expand Down
21 changes: 21 additions & 0 deletions pkg/tools/message.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tools
import (
"context"
"fmt"
"sync"
"sync/atomic"
)

Expand All @@ -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 {
Expand Down Expand Up @@ -50,13 +54,24 @@ 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.
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
}
Expand Down Expand Up @@ -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),
Expand Down