diff --git a/internal/handler/custom_agent.go b/internal/handler/custom_agent.go index 76a17366..12cc78ba 100644 --- a/internal/handler/custom_agent.go +++ b/internal/handler/custom_agent.go @@ -8,6 +8,7 @@ import ( "github.com/Tencent/WeKnora/internal/application/service" "github.com/Tencent/WeKnora/internal/errors" + "github.com/Tencent/WeKnora/internal/im" "github.com/Tencent/WeKnora/internal/logger" "github.com/Tencent/WeKnora/internal/types" "github.com/Tencent/WeKnora/internal/types/interfaces" @@ -18,6 +19,7 @@ import ( // CustomAgentHandler defines the HTTP handler for custom agent operations type CustomAgentHandler struct { service interfaces.CustomAgentService + imService *im.Service disabledRepo interfaces.TenantDisabledSharedAgentRepository // userService 仅用于 list 接口批量回填 creator_name,作用见 // KnowledgeBaseHandler.userService。 @@ -27,11 +29,13 @@ type CustomAgentHandler struct { // NewCustomAgentHandler creates a new custom agent handler instance func NewCustomAgentHandler( service interfaces.CustomAgentService, + imService *im.Service, disabledRepo interfaces.TenantDisabledSharedAgentRepository, userService interfaces.UserService, ) *CustomAgentHandler { return &CustomAgentHandler{ service: service, + imService: imService, disabledRepo: disabledRepo, userService: userService, } @@ -375,6 +379,20 @@ func (h *CustomAgentHandler) DeleteAgent(c *gin.Context) { logger.Infof(ctx, "Deleting custom agent, ID: %s", secutils.SanitizeForLog(id)) + tenantID, ok := types.TenantIDFromContext(ctx) + if !ok { + c.Error(errors.NewUnauthorizedError("Unauthorized")) + return + } + + if err := h.imService.DeleteChannelsByAgent(id, tenantID); err != nil { + logger.ErrorWithFields(ctx, err, map[string]interface{}{ + "agent_id": id, + }) + c.Error(errors.NewInternalServerError("Failed to delete agent IM channels")) + return + } + // Delete the agent err := h.service.DeleteAgent(ctx, id) if err != nil { diff --git a/internal/im/service.go b/internal/im/service.go index ed03e454..b77c67ca 100644 --- a/internal/im/service.go +++ b/internal/im/service.go @@ -2248,19 +2248,25 @@ type ChannelWithAgent struct { // ListChannelsByTenant returns all non-deleted IM channels in the given tenant, // joined with custom_agents.name. Built-in agent IDs (whose rows may not exist // in custom_agents) produce an empty AgentName — the frontend can substitute a -// localized "builtin agent" label in that case. +// localized "builtin agent" label in that case. Channels whose custom agent was +// soft-deleted are excluded so overview lists stay consistent after agent removal. func (s *Service) ListChannelsByTenant(tenantID uint64) ([]ChannelWithAgent, error) { + builtinIDs := types.GetBuiltinAgentIDs() var rows []ChannelWithAgent - err := s.db.Table("im_channels AS c"). + q := s.db.Table("im_channels AS c"). Select(`c.id, c.tenant_id, c.agent_id, COALESCE(a.name, '') AS agent_name, c.platform, c.name, c.enabled, c.mode, c.output_mode, c.session_mode, c.bot_identity, c.created_at, c.updated_at`). Joins(`LEFT JOIN custom_agents AS a - ON a.id = c.agent_id AND a.tenant_id = c.tenant_id`). - Where("c.tenant_id = ? AND c.deleted_at IS NULL", tenantID). - Order("c.created_at DESC"). - Scan(&rows).Error + ON a.id = c.agent_id AND a.tenant_id = c.tenant_id AND a.deleted_at IS NULL`). + Where("c.tenant_id = ? AND c.deleted_at IS NULL", tenantID) + if len(builtinIDs) > 0 { + q = q.Where("a.id IS NOT NULL OR c.agent_id IN ?", builtinIDs) + } else { + q = q.Where("a.id IS NOT NULL") + } + err := q.Order("c.created_at DESC").Scan(&rows).Error if err != nil { return nil, err } @@ -2303,6 +2309,25 @@ func (s *Service) UpdateChannel(channel *IMChannel) error { return nil } +// DeleteChannelsByAgent stops and soft-deletes every IM channel bound to the +// given agent within the tenant. Used when a custom agent is removed so +// overview lists and running adapters do not outlive the agent. +func (s *Service) DeleteChannelsByAgent(agentID string, tenantID uint64) error { + var channels []IMChannel + if err := s.db.Where("agent_id = ? AND tenant_id = ? AND deleted_at IS NULL", agentID, tenantID). + Find(&channels).Error; err != nil { + return err + } + for i := range channels { + s.StopChannel(channels[i].ID) + } + if len(channels) == 0 { + return nil + } + return s.db.Where("agent_id = ? AND tenant_id = ? AND deleted_at IS NULL", agentID, tenantID). + Delete(&IMChannel{}).Error +} + // DeleteChannel soft-deletes a channel and stops it. Only deletes if the channel belongs to the given tenant. func (s *Service) DeleteChannel(channelID string, tenantID uint64) error { s.StopChannel(channelID)