bugfix: fix duplicate Telegram message sending

Two issues caused duplicate messages to be sent to users:

1. System messages (from subagent): processSystemMessage set SendResponse:true,
   causing runAgentLoop to publish outbound. Then Run() also published outbound
   using the returned response string, resulting in two identical messages.
   Fix: processSystemMessage now returns empty string since runAgentLoop already
   handles the send.

2. Message tool double-send: When LLM called the "message" tool during
   processing, it published outbound immediately. Then Run() published the
   final response again. Fix: Track whether MessageTool sent a message in the
   current round (sentInRound flag, reset on each SetContext call). Run()
   checks HasSentInRound() before publishing to avoid duplicates.
This commit is contained in:
Zhaoyikaiii
2026-02-13 14:41:21 +08:00
parent 42e0e588dd
commit 132fe7db51
2 changed files with 30 additions and 7 deletions

View File

@@ -128,11 +128,22 @@ func (al *AgentLoop) Run(ctx context.Context) error {
} }
if response != "" { if response != "" {
al.bus.PublishOutbound(bus.OutboundMessage{ // Check if the message tool already sent a response during this round.
Channel: msg.Channel, // If so, skip publishing to avoid duplicate messages to the user.
ChatID: msg.ChatID, alreadySent := false
Content: response, 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 // Use the origin session for context
sessionKey := fmt.Sprintf("%s:%s", originChannel, originChatID) sessionKey := fmt.Sprintf("%s:%s", originChannel, originChatID)
// Process as system message with routing back to origin // Process as system message with routing back to origin.
return al.runAgentLoop(ctx, processOptions{ // 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, SessionKey: sessionKey,
Channel: originChannel, Channel: originChannel,
ChatID: originChatID, ChatID: originChatID,
@@ -228,6 +241,8 @@ func (al *AgentLoop) processSystemMessage(ctx context.Context, msg bus.InboundMe
EnableSummary: false, EnableSummary: false,
SendResponse: true, // Send response back to original channel 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. // runAgentLoop is the core message processing logic.

View File

@@ -11,6 +11,7 @@ type MessageTool struct {
sendCallback SendCallback sendCallback SendCallback
defaultChannel string defaultChannel string
defaultChatID string defaultChatID string
sentInRound bool // Tracks whether a message was sent in the current processing round
} }
func NewMessageTool() *MessageTool { func NewMessageTool() *MessageTool {
@@ -49,6 +50,12 @@ func (t *MessageTool) Parameters() map[string]interface{} {
func (t *MessageTool) SetContext(channel, chatID string) { func (t *MessageTool) SetContext(channel, chatID string) {
t.defaultChannel = channel t.defaultChannel = channel
t.defaultChatID = chatID 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) { 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 return fmt.Sprintf("Error sending message: %v", err), nil
} }
t.sentInRound = true
return fmt.Sprintf("Message sent to %s:%s", channel, chatID), nil return fmt.Sprintf("Message sent to %s:%s", channel, chatID), nil
} }