diff --git a/pkg/agent/loop.go b/pkg/agent/loop.go index fac2856..dad920d 100644 --- a/pkg/agent/loop.go +++ b/pkg/agent/loop.go @@ -128,11 +128,22 @@ func (al *AgentLoop) Run(ctx context.Context) error { } if response != "" { - al.bus.PublishOutbound(bus.OutboundMessage{ - Channel: msg.Channel, - ChatID: msg.ChatID, - Content: response, - }) + // Check if the message tool already sent a response during this round. + // If so, skip publishing to avoid duplicate messages to the user. + alreadySent := false + if tool, ok := al.tools.Get("message"); ok { + if mt, ok := tool.(*tools.MessageTool); ok { + alreadySent = mt.HasSentInRound() + } + } + + if !alreadySent { + al.bus.PublishOutbound(bus.OutboundMessage{ + Channel: msg.Channel, + ChatID: msg.ChatID, + Content: response, + }) + } } } } @@ -218,8 +229,10 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe // Use the origin session for context sessionKey := fmt.Sprintf("%s:%s", originChannel, originChatID) - // Process as system message with routing back to origin - return al.runAgentLoop(ctx, processOptions{ + // Process as system message with routing back to origin. + // SendResponse: true means runAgentLoop will publish the outbound message itself, + // so we return empty string to prevent Run() from publishing a duplicate. + _, err := al.runAgentLoop(ctx, processOptions{ SessionKey: sessionKey, Channel: originChannel, ChatID: originChatID, @@ -228,6 +241,8 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe EnableSummary: false, SendResponse: true, // Send response back to original channel }) + // Return empty string: response was already sent via bus in runAgentLoop + return "", err } // runAgentLoop is the core message processing logic. diff --git a/pkg/tools/message.go b/pkg/tools/message.go index e090234..7cb8ff7 100644 --- a/pkg/tools/message.go +++ b/pkg/tools/message.go @@ -11,6 +11,7 @@ type MessageTool struct { sendCallback SendCallback defaultChannel string defaultChatID string + sentInRound bool // Tracks whether a message was sent in the current processing round } func NewMessageTool() *MessageTool { @@ -49,6 +50,12 @@ func (t *MessageTool) Parameters() map[string]interface{} { func (t *MessageTool) SetContext(channel, chatID string) { t.defaultChannel = channel t.defaultChatID = chatID + t.sentInRound = false // Reset send tracking for new processing round +} + +// HasSentInRound returns true if the message tool sent a message during the current round. +func (t *MessageTool) HasSentInRound() bool { + return t.sentInRound } func (t *MessageTool) SetSendCallback(callback SendCallback) { @@ -83,5 +90,6 @@ func (t *MessageTool) Execute(ctx context.Context, args map[string]interface{}) return fmt.Sprintf("Error sending message: %v", err), nil } + t.sentInRound = true return fmt.Sprintf("Message sent to %s:%s", channel, chatID), nil }