From 89af3b251137698cfc0be6d284a323c21301dd31 Mon Sep 17 00:00:00 2001 From: smallwhite Date: Mon, 30 Mar 2026 15:01:01 +0800 Subject: [PATCH] fix(tools): message tool no longer suppresses reply to originating chat When the message tool sent to a different chat (e.g., a group), the agent's final response to the originating chat was incorrectly skipped because HasSentInRound() was a simple bool that didn't distinguish targets. Replace with HasSentTo(channel, chatID) that tracks all send targets per round and only suppresses when the target matches. Fixes cross-conversation message causing "Processing..." to hang. --- pkg/agent/loop.go | 10 +++++----- pkg/tools/message.go | 38 +++++++++++++++++++++++++++++++++----- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index ef2951365..a32d8d5bf 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -608,21 +608,21 @@ func (al *AgentLoop) PublishResponseIfNeeded(ctx context.Context, channel, chatI return } - alreadySent := false + alreadySentToSameChat := false defaultAgent := al.GetRegistry().GetDefaultAgent() if defaultAgent != nil { if tool, ok := defaultAgent.Tools.Get("message"); ok { if mt, ok := tool.(*tools.MessageTool); ok { - alreadySent = mt.HasSentInRound() + alreadySentToSameChat = mt.HasSentTo(channel, chatID) } } } - if alreadySent { + if alreadySentToSameChat { logger.DebugCF( "agent", - "Skipped outbound (message tool already sent)", - map[string]any{"channel": channel}, + "Skipped outbound (message tool already sent to same chat)", + map[string]any{"channel": channel, "chat_id": chatID}, ) return } diff --git a/pkg/tools/message.go b/pkg/tools/message.go index 438ceeddd..e20edbd20 100644 --- a/pkg/tools/message.go +++ b/pkg/tools/message.go @@ -3,14 +3,21 @@ package tools import ( "context" "fmt" - "sync/atomic" + "sync" ) type SendCallback func(channel, chatID, content string) error +// sentTarget records the channel+chatID that the message tool sent to. +type sentTarget struct { + Channel string + ChatID string +} + type MessageTool struct { sendCallback SendCallback - sentInRound atomic.Bool // Tracks whether a message was sent in the current processing round + mu sync.Mutex + sentTargets []sentTarget // Tracks all targets sent to in the current round } func NewMessageTool() *MessageTool { @@ -49,12 +56,30 @@ func (t *MessageTool) Parameters() map[string]any { // ResetSentInRound resets the per-round send tracker. // 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.sentTargets = t.sentTargets[:0] + 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() + t.mu.Lock() + defer t.mu.Unlock() + return len(t.sentTargets) > 0 +} + +// HasSentTo returns true if the message tool sent to the specific channel+chatID +// during the current round. Used by PublishResponseIfNeeded to avoid suppressing +// the final response when the message tool only sent to a different conversation. +func (t *MessageTool) HasSentTo(channel, chatID string) bool { + t.mu.Lock() + defer t.mu.Unlock() + for _, st := range t.sentTargets { + if st.Channel == channel && st.ChatID == chatID { + return true + } + } + return false } func (t *MessageTool) SetSendCallback(callback SendCallback) { @@ -93,7 +118,10 @@ func (t *MessageTool) Execute(ctx context.Context, args map[string]any) *ToolRes } } - t.sentInRound.Store(true) + t.mu.Lock() + t.sentTargets = append(t.sentTargets, sentTarget{Channel: channel, ChatID: chatID}) + t.mu.Unlock() + // Silent: user already received the message directly return &ToolResult{ ForLLM: fmt.Sprintf("Message sent to %s:%s", channel, chatID),