WeKnora deployment source with the reviewed wiki identity, summary and recovery fixes
Anydoc / anydoc parser engine (push) Canceled after 0s
App / Format, vet, test, and build (push) Canceled after 0s
CLI / test (macos-latest / go 1.26) (push) Canceled after 0s
CLI / test (ubuntu-latest / go 1.26) (push) Canceled after 0s
CLI / test (windows-latest / go 1.26) (push) Canceled after 0s
Build and Push Docker Image / build-ui (push) Canceled after 0s
Build and Push Docker Image / build-docreader (push) Canceled after 0s
Build and Push Docker Image / build-sandbox (push) Canceled after 0s
Build and Push Docker Image / build-app (amd64, linux/amd64, ubuntu-latest) (push) Canceled after 0s
Build and Push Docker Image / build-app (arm64, linux/arm64, ubuntu-24.04-arm) (push) Canceled after 0s
DocReader / Python tests and Go client tests (push) Canceled after 0s
Build & Publish (dsh-weknora) / Typecheck, test, pack (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (pinned) (push) Canceled after 0s
Build & Publish (dsh-weknora) / End-to-end inside dsh (latest, advisory) (push) Canceled after 0s
Frontend / Test, type-check, and build (push) Canceled after 0s
Go Lint Cache / warm lint cache (.) (push) Canceled after 0s
Go Lint Cache / warm lint cache (cli) (push) Canceled after 0s
Go Lint Cache / warm lint cache (client) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.10) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.11) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.12) (push) Canceled after 0s
Build & Publish (mcp-server) / Test (Python 3.13) (push) Canceled after 0s
Build and Push Docker Image / merge (push) Canceled after 0s
Build & Publish (dsh-weknora) / Publish to npm (push) Canceled after 0s
Build & Publish (mcp-server) / Build sdist + wheel (push) Canceled after 0s
Build & Publish (mcp-server) / Publish to PyPI (push) Canceled after 0s

This commit is contained in:
yawan
2026-09-22 16:19:06 +08:00
commit f7fc992f57
3929 changed files with 1006607 additions and 0 deletions
@@ -0,0 +1,237 @@
package repository
import (
"context"
"fmt"
"github.com/Tencent/WeKnora/internal/accesspolicy"
apperrors "github.com/Tencent/WeKnora/internal/errors"
"strings"
"github.com/Tencent/WeKnora/internal/types"
"github.com/google/uuid"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// FilterDigitalHumanAgents protects persona-bearing native agents in ordinary
// tenant access paths. Explicit organization shares use their verified share path.
func (r *customAgentRepository) FilterDigitalHumanAgents(ctx context.Context, agents []*types.CustomAgent) ([]*types.CustomAgent, error) {
if len(agents) == 0 {
return agents, nil
}
ids := make([]string, 0, len(agents))
for _, agent := range agents {
ids = append(ids, agent.ID)
}
var bindings []types.DigitalHumanBinding
if err := r.db.WithContext(ctx).Where("agent_id IN ?", ids).Find(&bindings).Error; err != nil {
return nil, err
}
byAgent := make(map[string]types.DigitalHumanBinding, len(bindings))
humanIDs := make([]string, 0, len(bindings))
for _, binding := range bindings {
byAgent[binding.AgentID] = binding
humanIDs = append(humanIDs, binding.DigitalHumanID)
}
userID, _ := types.UserIDFromContext(ctx)
tenantID, _ := types.TenantIDFromContext(ctx)
var humans []types.DigitalHuman
if len(humanIDs) > 0 {
if err := r.db.WithContext(ctx).Select("id", "name", "active_version").Where("tenant_id = ? AND id IN ?", tenantID, humanIDs).Find(&humans).Error; err != nil {
return nil, err
}
}
byHuman := make(map[string]types.DigitalHuman, len(humans))
for _, human := range humans {
byHuman[human.ID] = human
}
admin := types.IsSystemAdminFromContext(ctx) || types.TenantRoleFromContext(ctx).HasPermission(types.TenantRoleAdmin)
result := make([]*types.CustomAgent, 0, len(agents))
for _, agent := range agents {
binding, bound := byAgent[agent.ID]
// Copies preserve the persona snapshot but do not have a live binding.
// Keep those snapshots private until the creator removes that persona.
ownerID, ownerTenant := binding.OwnerID, binding.TenantID
private := bound || strings.Contains(agent.Config.SystemPrompt, "<!-- weknora:digital-human:")
if !bound {
ownerID, ownerTenant = agent.CreatedBy, agent.TenantID
}
shared := accesspolicy.Readable(ctx, "agents", agent.ID, false, false) || accesspolicy.CanExecuteDependency(ctx, agent.TenantID, "agents", agent.ID, agent.CreatedBy) || (bound && accesspolicy.Readable(ctx, "digital-humans", binding.DigitalHumanID, false, false))
if !private || (tenantID == ownerTenant && (admin || shared || (userID != "" && !types.IsSyntheticUserID(userID) && userID == ownerID))) {
agent.DigitalHuman = nil
if human, ok := byHuman[binding.DigitalHumanID]; bound && ok {
agent.DigitalHuman = &types.AgentDigitalHumanSummary{ID: human.ID, Name: human.Name, AppliedVersion: binding.AppliedVersion, ActiveVersion: human.ActiveVersion}
}
result = append(result, agent)
}
}
return result, nil
}
// Re-read the binding and persona before each execution. A user's installed
// snapshot does not turn a revoked shared persona into their own resource.
func (r *customAgentRepository) CheckDigitalHumanAgentExecution(ctx context.Context, agent *types.CustomAgent) error {
if !strings.Contains(agent.Config.SystemPrompt, "<!-- weknora:digital-human:") {
return nil
}
denied := apperrors.NewForbiddenError("此 Agent 的数字人已删除、解绑或授权已撤销,请重新选择可用数字人")
if tenant, ok := types.TenantIDFromContext(ctx); !ok || tenant != agent.TenantID || tenant == 0 {
return denied
}
var binding types.DigitalHumanBinding
if err := r.db.WithContext(ctx).Where("tenant_id = ? AND agent_id = ? AND owner_id = ?", agent.TenantID, agent.ID, agent.CreatedBy).First(&binding).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return denied
}
return err
}
var human types.DigitalHuman
if err := r.db.WithContext(ctx).Where("tenant_id = ? AND id = ?", agent.TenantID, binding.DigitalHumanID).First(&human).Error; err != nil {
if err == gorm.ErrRecordNotFound {
return denied
}
return err
}
if !strings.Contains(agent.Config.SystemPrompt, fmt.Sprintf("<!-- weknora:digital-human:%s:start -->", human.ID)) || strings.Count(agent.Config.SystemPrompt, "<!-- weknora:digital-human:") != 2 {
return denied
}
if binding.AppliedVersion <= 0 || binding.AppliedVersion != human.ActiveVersion || !accesspolicy.Allowed(ctx, "digital-humans.read") || accesspolicy.Hidden(ctx, "digital-humans", human.ID) {
return denied
}
userID, _ := types.UserIDFromContext(ctx)
admin := types.IsSystemAdminFromContext(ctx) || types.TenantRoleFromContext(ctx).HasPermission(types.TenantRoleAdmin)
ownedDependency := human.OwnerID != "" && human.OwnerID == agent.CreatedBy
if !admin && !ownedDependency && !accesspolicy.Readable(ctx, "digital-humans", human.ID, human.OwnerID == userID, false) {
return denied
}
return nil
}
func (r *tenantSkillRepository) CheckDigitalHumanAgentExecution(ctx context.Context, agent *types.CustomAgent) error {
return (&customAgentRepository{db: r.db}).CheckDigitalHumanAgentExecution(ctx, agent)
}
// An allowed copy must keep its live persona binding so later review,
// revocation and deletion still apply to the installed profile.
func (r *customAgentRepository) CreateAgentCopy(ctx context.Context, sourceID string, target *types.CustomAgent) error {
if !strings.Contains(target.Config.SystemPrompt, "<!-- weknora:digital-human:") {
return r.CreateAgent(ctx, target)
}
return r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
var original types.DigitalHumanBinding
if err := tx.Where("tenant_id = ? AND agent_id = ?", target.TenantID, sourceID).First(&original).Error; err != nil {
return err
}
var human types.DigitalHuman
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("tenant_id = ? AND id = ?", target.TenantID, original.DigitalHumanID).First(&human).Error; err != nil {
return err
}
var source types.CustomAgent
if err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).Where("tenant_id = ? AND id = ?", target.TenantID, sourceID).First(&source).Error; err != nil {
return err
}
var current types.DigitalHumanBinding
if err := tx.Where("tenant_id = ? AND agent_id = ?", target.TenantID, sourceID).First(&current).Error; err != nil {
return err
}
if current.DigitalHumanID != human.ID || current.AppliedVersion != human.ActiveVersion {
return types.ErrDigitalHumanConflict
}
repo := &customAgentRepository{db: tx}
if err := repo.CheckDigitalHumanAgentCopy(ctx, sourceID, target.TenantID); err != nil {
return err
}
if err := repo.CheckDigitalHumanAgentExecution(ctx, &source); err != nil {
return err
}
if source.Config.SystemPrompt != target.Config.SystemPrompt {
return types.ErrDigitalHumanConflict
}
if err := tx.Create(target).Error; err != nil {
return err
}
binding := types.DigitalHumanBinding{ID: uuid.NewString(), TenantID: target.TenantID, OwnerID: target.CreatedBy, DigitalHumanID: human.ID, AgentID: target.ID, BasePrompt: removeDigitalHumanPrompt(target.Config.SystemPrompt, human.ID), AppliedVersion: human.ActiveVersion}
return tx.Create(&binding).Error
})
}
func (r *customAgentRepository) CheckDigitalHumanAgentCopy(ctx context.Context, id string, tenantID uint64) error {
if types.IsSystemAdminFromContext(ctx) || types.TenantRoleFromContext(ctx).HasPermission(types.TenantRoleAdmin) {
return nil
}
userID, _ := types.UserIDFromContext(ctx)
var count int64
err := r.db.WithContext(ctx).Table("digital_human_bindings AS binding").
Joins("JOIN digital_humans AS human ON human.id = binding.digital_human_id AND human.tenant_id = binding.tenant_id").
Where("binding.agent_id = ? AND binding.tenant_id = ? AND human.owner_id <> ?", id, tenantID, userID).
Count(&count).Error
if err != nil {
return err
}
if count > 0 {
return apperrors.NewForbiddenError("此 Agent 挂载了共享数字人,只能使用,不能复制其档案")
}
return nil
}
func (r *customAgentRepository) CheckDigitalHumanAgentDeletion(ctx context.Context, id string, tenantID uint64) error {
var count int64
if err := r.db.WithContext(ctx).Model(&types.DigitalHuman{}).Where("primary_agent_id = ? AND tenant_id = ?", id, tenantID).Count(&count).Error; err != nil {
return err
}
if count > 0 {
return types.ErrDigitalHumanPrimaryAgent
}
return nil
}
func (r *customAgentRepository) DeleteAgentWithDigitalHumanBindings(ctx context.Context, id string, tenantID uint64) ([]string, error) {
var channelIDs []string
err := r.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
repo := &customAgentRepository{db: tx}
if err := repo.CheckDigitalHumanAgentDeletion(ctx, id, tenantID); err != nil {
return err
}
if err := repo.DeleteAgent(ctx, id, tenantID); err != nil {
return err
}
if err := detachFusionAgent(tx, id, tenantID); err != nil {
return err
}
var err error
channelIDs, err = softDeleteAgentIMChannels(tx, id, tenantID)
if err != nil {
return err
}
return tx.Where("agent_id = ? AND tenant_id = ?", id, tenantID).Delete(&types.DigitalHumanBinding{}).Error
})
if err != nil {
return nil, err
}
return channelIDs, nil
}
func detachFusionAgent(tx *gorm.DB, id string, tenantID uint64) error {
if tx.Dialector.Name() == "postgres" {
return tx.Exec(`UPDATE custom_agents SET config = jsonb_set(config::jsonb, '{fusion_agent_ids}', (config::jsonb->'fusion_agent_ids') - ?), updated_at = CURRENT_TIMESTAMP WHERE tenant_id = ? AND deleted_at IS NULL AND jsonb_typeof(config::jsonb->'fusion_agent_ids') = 'array' AND jsonb_exists(config::jsonb->'fusion_agent_ids', ?)`, id, tenantID, id).Error
}
var agents []types.CustomAgent
if err := tx.Where("tenant_id = ?", tenantID).Find(&agents).Error; err != nil {
return err
}
for _, agent := range agents {
ids := make([]string, 0, len(agent.Config.FusionAgentIDs))
for _, fusionID := range agent.Config.FusionAgentIDs {
if fusionID != id {
ids = append(ids, fusionID)
}
}
if len(ids) != len(agent.Config.FusionAgentIDs) {
agent.Config.FusionAgentIDs = ids
if err := tx.Model(&agent).Update("config", agent.Config).Error; err != nil {
return err
}
}
}
return nil
}