package workbench import ( "context" "fmt" "strings" "time" "github.com/Tencent/WeKnora/internal/accesspolicy" "github.com/Tencent/WeKnora/internal/types" "gorm.io/gorm" "gorm.io/gorm/clause" ) type memoryReviewRequest struct { Status string `json:"status"` Content string `json:"content"` ReconfirmLegacy bool `json:"reconfirm_legacy"` ConfirmPersonalScope bool `json:"confirm_personal_scope"` } func legacyMemoryNeedsConfirmation(row *PendingMemory) bool { return row.Status == "accepted" && row.LegacyHistoryID != "" && row.NativeMemoryID == "" } func (s *Service) legacyMemoryAgent(ctx context.Context, row *PendingMemory) (*types.CustomAgent, error) { if s.agents == nil || row.AgentID == "" { return nil, fmt.Errorf("%w: 原 Agent 已删除或无权访问,历史记录仍保留", ErrConflict) } agent, err := s.agents.GetAgentByID(ctx, row.AgentID) if err != nil || agent == nil || agent.TenantID != row.TenantID || agent.DeletedAt.Valid { return nil, fmt.Errorf("%w: 原 Agent 已删除或无权访问,历史记录仍保留", ErrConflict) } if !agent.Enabled { return nil, fmt.Errorf("%w: 原 Agent 已停用,历史记录仍保留", ErrConflict) } if agent.Config.MemoryEnabled != nil && !*agent.Config.MemoryEnabled { return nil, fmt.Errorf("%w: 原 Agent 的记忆已关闭", ErrConflict) } if s.memories == nil || !s.memories.MemoryAvailable(types.ApplyAgentMemoryPreference(ctx, agent.Config.MemoryEnabled)) { return nil, fmt.Errorf("%w: 请先启用工作区和个人记忆", ErrConflict) } return agent, nil } func (s *Service) decorateLegacyMemories(ctx context.Context, rows []PendingMemory) { for i := range rows { row := &rows[i] row.CanReconfirm, row.ReconfirmationUnavailableReason = false, "" if !legacyMemoryNeedsConfirmation(row) { continue } if !accesspolicy.Allowed(ctx, "agents.memory") { row.ReconfirmationUnavailableReason = "没有记忆管理权限" continue } if _, err := s.legacyMemoryAgent(ctx, row); err != nil { row.ReconfirmationUnavailableReason = strings.TrimPrefix(err.Error(), ErrConflict.Error()+": ") } else { row.CanReconfirm = true } } } // The original imported row and source reference stay intact. Its native link // is committed with the memory and retained even if that memory is later deleted. func (s *Service) reconfirmLegacyMemory(ctx context.Context, tx *gorm.DB, row *PendingMemory, body memoryReviewRequest) error { if body.Status != "accepted" || !body.ConfirmPersonalScope || strings.TrimSpace(body.Content) == "" { return fmt.Errorf("%w: 请编辑记忆并确认适用于当前工作区的个人记忆", ErrInvalid) } if row.Status != "accepted" || row.LegacyHistoryID == "" { return ErrConflict } if row.NativeMemoryID == "" { if _, err := s.legacyMemoryAgent(ctx, row); err != nil { return err } var agent types.CustomAgent if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ? AND tenant_id = ?", row.AgentID, row.TenantID).Take(&agent).Error; err != nil { if err == gorm.ErrRecordNotFound { return fmt.Errorf("%w: 原 Agent 已删除,历史记录仍保留", ErrConflict) } return err } if !agent.Enabled { return fmt.Errorf("%w: 原 Agent 已停用,历史记录仍保留", ErrConflict) } if agent.Config.MemoryEnabled != nil && !*agent.Config.MemoryEnabled { return fmt.Errorf("%w: 原 Agent 的记忆已关闭", ErrConflict) } ctx = types.ApplyAgentMemoryPreference(ctx, agent.Config.MemoryEnabled) } if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("id = ? AND tenant_id = ? AND owner_id = ?", row.ID, row.TenantID, row.OwnerID).Take(row).Error; err != nil { return err } if row.Status != "accepted" || row.LegacyHistoryID == "" { return ErrConflict } if row.NativeMemoryID != "" { return nil } content := strings.TrimSpace(body.Content) if len([]rune(content)) > types.MemoryContentMaxRunes { return fmt.Errorf("%w: 长期记忆最多 %d 字,请精简后再确认;历史记录仍保留", ErrInvalid, types.MemoryContentMaxRunes) } if s.rememberInTransaction == nil { return fmt.Errorf("%w: 记忆服务未初始化", ErrInvalid) } stored, err := s.rememberInTransaction(ctx, tx, types.MemoryItem{ Kind: types.MemoryKindPreference, Content: content, Origin: types.MemoryOriginExplicit, Importance: 3, SourceMessageID: row.ID, }) if err != nil { return err } principal, ok := types.PrincipalFromContext(ctx) if !ok || principal.Type != types.PrincipalWebUser || principal.ID != row.OwnerID || stored == nil || stored.ID == "" || stored.Status != types.MemoryStatusActive || stored.TenantID != row.TenantID || stored.SubjectID != principal.StorageID() { return fmt.Errorf("%w: 记忆尚未写入,请重试", ErrInvalid) } now := time.Now() row.NativeMemoryID, row.ReconfirmedContent, row.ReconfirmedAt = stored.ID, stored.Content, &now row.ReviewedBy, row.UpdatedAt = row.OwnerID, now return tx.Save(row).Error }