feat(im): integrate IM service for custom agent deletion

- Add IM service dependency to CustomAgentHandler for managing agent-related IM channels.
- Implement DeleteChannelsByAgent method to stop and soft-delete IM channels associated with a custom agent upon its removal.
- Update ListChannelsByTenant to exclude soft-deleted custom agents, ensuring consistent overview lists after agent removal.
This commit is contained in:
wizardchen
2026-06-22 17:14:49 +08:00
committed by lyingbug
parent ddc5460988
commit 632cd55018
2 changed files with 49 additions and 6 deletions
+18
View File
@@ -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 {
+31 -6
View File
@@ -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)