diff --git a/pkg/agent/agent_outbound.go b/pkg/agent/agent_outbound.go index 1728f6f793..f4a01adfda 100644 --- a/pkg/agent/agent_outbound.go +++ b/pkg/agent/agent_outbound.go @@ -56,6 +56,16 @@ func (al *AgentLoop) PublishResponseIfNeeded(ctx context.Context, channel, chatI } if alreadySentToSameChat { + if al.channelManager != nil && channel != "" && chatID != "" { + dismissCtx, dismissCancel := context.WithTimeout(ctx, 5*time.Second) + al.channelManager.DismissToolFeedback( + dismissCtx, + channel, + chatID, + nil, + ) + dismissCancel() + } logger.DebugCF( "agent", "Skipped outbound (message tool already sent to same chat)", diff --git a/pkg/agent/agent_test.go b/pkg/agent/agent_test.go index a75919912f..7a869ec94c 100644 --- a/pkg/agent/agent_test.go +++ b/pkg/agent/agent_test.go @@ -57,6 +57,38 @@ func (f *fakeMediaChannel) SendMedia(ctx context.Context, msg bus.OutboundMediaM return nil, nil } +type recordingChannelManager struct { + dismissed []string +} + +func (m *recordingChannelManager) GetChannel(name string) (channels.Channel, bool) { + return nil, false +} + +func (m *recordingChannelManager) GetEnabledChannels() []string { + return nil +} + +func (m *recordingChannelManager) InvokeTypingStop(channel, chatID string) {} + +func (m *recordingChannelManager) SendMessage(ctx context.Context, msg bus.OutboundMessage) error { + return nil +} + +func (m *recordingChannelManager) SendMedia(ctx context.Context, msg bus.OutboundMediaMessage) error { + return nil +} + +func (m *recordingChannelManager) SendPlaceholder(ctx context.Context, channel, chatID string) bool { + return false +} + +func (m *recordingChannelManager) DismissToolFeedback( + ctx context.Context, channel, chatID string, outboundCtx *bus.InboundContext, +) { + m.dismissed = append(m.dismissed, fmt.Sprintf("%s:%s", channel, chatID)) +} + func newStartedTestChannelManager( t *testing.T, msgBus *bus.MessageBus, @@ -214,6 +246,44 @@ func TestNewAgentLoop_DoesNotRegisterWebSearchTool_WhenNoReadyProviders(t *testi } } +func TestPublishResponseIfNeeded_DismissesToolFeedbackWhenMessageToolAlreadySent(t *testing.T) { + al, msgBus, provider, sessions, cleanup := newTestAgentLoop(t) + defer cleanup() + _ = msgBus + _ = provider + _ = sessions + + cm := &recordingChannelManager{} + al.channelManager = cm + + defaultAgent := al.registry.GetDefaultAgent() + if defaultAgent == nil { + t.Fatal("expected default agent") + } + mt := tools.NewMessageTool() + mt.SetSendCallback(func(ctx context.Context, channel, chatID, content, replyToMessageID string) error { + return nil + }) + defaultAgent.Tools.Register(mt) + + result := mt.Execute( + tools.WithToolSessionContext(context.Background(), "main", "session-1", nil), + map[string]any{ + "content": "ack", + "channel": "telegram", + "chat_id": "-100123", + }, + ) + if result == nil || result.IsError { + t.Fatalf("message tool execute failed: %+v", result) + } + al.PublishResponseIfNeeded(context.Background(), "telegram", "-100123", "session-1", "final reply") + + if got := cm.dismissed; len(got) != 1 || got[0] != "telegram:-100123" { + t.Fatalf("dismissed = %v, want [telegram:-100123]", got) + } +} + func TestProcessMessage_IncludesCurrentSenderInDynamicContext(t *testing.T) { tmpDir, err := os.MkdirTemp("", "agent-test-*") if err != nil {