fix(tools): track all sent chat_ids to avoid missed duplicate suppression

Replace single sentChatID (atomic.Value) with a set of chat_ids so that
when the message tool sends to multiple chats in one round, the final
reply is correctly suppressed for any chat that was already sent to.
This commit is contained in:
青柠 2026-03-11 15:26:01 +08:00
parent 5fb488ca31
commit f02c3a35ed
2 changed files with 21 additions and 9 deletions

View file

@ -346,7 +346,7 @@ func (al *AgentLoop) Run(ctx context.Context) error {
if defaultAgent != nil {
if tool, ok := defaultAgent.Tools.Get("message"); ok {
if mt, ok := tool.(*tools.MessageTool); ok {
alreadySent = mt.HasSentInRound() && mt.SentToChatID() == msg.ChatID
alreadySent = mt.HasSentInRound() && mt.HasSentToChatID(msg.ChatID)
}
}
}

View file

@ -3,6 +3,7 @@ package tools
import (
"context"
"fmt"
"sync"
"sync/atomic"
)
@ -10,8 +11,10 @@ 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
sentChatID atomic.Value // Tracks the chat_id that the message was sent to in the current round
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 {
@ -51,7 +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.sentChatID.Store("")
t.mu.Lock()
t.sentChatIDs = nil
t.mu.Unlock()
}
// HasSentInRound returns true if the message tool sent a message during the current round.
@ -59,10 +64,12 @@ func (t *MessageTool) HasSentInRound() bool {
return t.sentInRound.Load()
}
// SentToChatID returns the chat_id that the message was sent to in the current round.
func (t *MessageTool) SentToChatID() string {
v, _ := t.sentChatID.Load().(string)
return v
// 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) {
@ -102,7 +109,12 @@ func (t *MessageTool) Execute(ctx context.Context, args map[string]any) *ToolRes
}
t.sentInRound.Store(true)
t.sentChatID.Store(chatID)
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),