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
+197
View File
@@ -0,0 +1,197 @@
package workbench
import (
"context"
"fmt"
"regexp"
"strings"
"time"
"github.com/Tencent/WeKnora/internal/types"
)
const fusionPromptRuneLimit = 16000
var digitalHumanPromptMarker = regexp.MustCompile(`<!-- weknora:digital-human:[^<>\s]+:start -->`)
func (s *Service) readableFusion(ctx context.Context, id string) (*types.CustomAgent, error) {
a, err := s.agents.GetAgentByID(ctx, id)
tenant, _ := types.TenantIDFromContext(ctx)
if err != nil || a == nil || a.TenantID != tenant {
return nil, fmt.Errorf("%w: 融合 Agent 不存在或无权使用,请更新任务配置", ErrForbidden)
}
a.EnsureDefaults()
if !a.Enabled || a.Config.Role != types.AgentRoleProduction {
return nil, fmt.Errorf("%w: 融合 Agent 已停用或不再承担内容生产职责", ErrInvalid)
}
return a, nil
}
func (s *Service) validateFrozenFusions(ctx context.Context, fusions []FusionSnapshot) error {
for _, saved := range fusions {
a, err := s.readableFusion(ctx, saved.ID)
if err != nil {
return err
}
// An unbound or replaced digital human must not survive in a task snapshot.
for _, marker := range digitalHumanPromptMarker.FindAllString(saved.SystemPrompt, -1) {
if !strings.Contains(a.Config.SystemPrompt, marker) {
return fmt.Errorf("%w: 融合 Agent 的数字人绑定已变更,请更新任务配置", ErrForbidden)
}
}
copy := *a
copy.Config.InstructionSkillIDs = append([]string(nil), saved.SkillIDs...)
if len(saved.SkillIDs) > 0 && s.instructions == nil {
return fmt.Errorf("%w: 融合 Agent 的文字技能服务不可用", ErrInvalid)
}
if s.instructions != nil {
if _, err := s.instructions.ResolveAgentInstructions(ctx, &copy); err != nil {
return err
}
}
}
return nil
}
func (s *Service) prepareFusions(ctx context.Context, primary *types.CustomAgent, saved AgentSnapshot, frozen bool, query string) ([]FusionSnapshot, []*types.MemoryItem, error) {
if err := primary.Config.ValidateFusion(primary.ID); err != nil {
return nil, nil, fmt.Errorf("%w: %s", ErrInvalid, err)
}
if frozen {
if err := s.validateFrozenFusions(ctx, saved.Fusions); err != nil {
return nil, nil, err
}
return saved.Fusions, saved.FusionMemories, nil
}
fusions := make([]FusionSnapshot, 0, len(primary.Config.FusionAgentIDs))
memoryEnabled := false
for _, id := range primary.Config.FusionAgentIDs {
a, err := s.readableFusion(ctx, id)
if err != nil {
return nil, nil, err
}
instructions := ""
if len(a.Config.InstructionSkillIDs) > 0 && s.instructions == nil {
return nil, nil, fmt.Errorf("%w: 融合 Agent 的文字技能服务不可用", ErrInvalid)
}
if s.instructions != nil {
instructions, err = s.instructions.ResolveAgentInstructions(ctx, a)
if err != nil {
return nil, nil, err
}
}
fusions = append(fusions, FusionSnapshot{ID: id, Name: a.Name, SystemPrompt: a.Config.SystemPrompt,
Instructions: instructions, SkillIDs: append([]string(nil), a.Config.InstructionSkillIDs...), MemoryEnabled: a.Config.MemoryEnabled})
memoryEnabled = memoryEnabled || a.Config.MemoryEnabled == nil || *a.Config.MemoryEnabled
}
var memories []*types.MemoryItem
if memoryEnabled && (primary.Config.MemoryEnabled == nil || *primary.Config.MemoryEnabled) && s.memories != nil {
memoryCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
recall := s.memories.Recall(memoryCtx, boundedFusionText(query, 2000))
cancel()
tenant, _ := types.TenantIDFromContext(ctx)
principal, _ := types.PrincipalFromContext(ctx)
seen := map[string]bool{}
for _, item := range recall.Items {
if item == nil || item.ID == "" || item.TenantID != tenant || item.SubjectID != principal.StorageID() || item.Status != types.MemoryStatusActive || item.Inferred || item.InvalidAt != nil || (item.ExpiresAt != nil && !item.ExpiresAt.After(time.Now())) || seen[item.Content] {
continue
}
copy := *item
memories = append(memories, &copy)
seen[item.Content] = true
if len(memories) == 8 {
break
}
}
}
return fusions, memories, nil
}
func boundedFusionText(text string, limit int) string {
runes := []rune(text)
if len(runes) <= limit {
return text
}
return string(runes[:limit])
}
// Native memories belong to the current principal, not to an Agent's owner.
// Reuse only the captured IDs and content; deleted/edited memories never fall back.
func (s *Service) currentFusionMemories(ctx context.Context, saved []*types.MemoryItem) []*types.MemoryItem {
if len(saved) == 0 || s.memories == nil || s.db == nil || !s.memories.MemoryAvailable(ctx) {
return nil
}
tenant, _ := types.TenantIDFromContext(ctx)
principal, ok := types.PrincipalFromContext(ctx)
if !ok || tenant == 0 {
return nil
}
ids := make([]string, 0, len(saved))
for _, item := range saved {
if item != nil && item.TenantID == tenant && item.SubjectID == principal.StorageID() {
ids = append(ids, item.ID)
}
}
var current []types.MemoryItem
if len(ids) == 0 || s.db.WithContext(ctx).Where("tenant_id = ? AND subject_id = ? AND id IN ? AND status = ? AND invalid_at IS NULL AND (expires_at IS NULL OR expires_at > ?)", tenant, principal.StorageID(), ids, types.MemoryStatusActive, time.Now()).Find(&current).Error != nil {
return nil
}
byID := map[string]types.MemoryItem{}
for _, item := range current {
byID[item.ID] = item
}
result := make([]*types.MemoryItem, 0, len(current))
for _, item := range saved {
if item == nil {
continue
}
if live, ok := byID[item.ID]; ok && !live.Inferred && live.Content == item.Content && live.Kind == item.Kind {
result = append(result, item)
}
}
return result
}
func (s *Service) requestFusionMemories(ctx context.Context, primary *types.CustomAgent, fusions []FusionSnapshot, saved []*types.MemoryItem) []*types.MemoryItem {
if len(saved) == 0 || primary.Config.MemoryEnabled != nil && !*primary.Config.MemoryEnabled {
return nil
}
live, err := s.agents.GetAgentByID(ctx, primary.ID)
if err != nil || live == nil || live.Config.MemoryEnabled != nil && !*live.Config.MemoryEnabled {
return nil
}
for _, fusion := range fusions {
if fusion.MemoryEnabled != nil && !*fusion.MemoryEnabled {
continue
}
live, err := s.readableFusion(ctx, fusion.ID)
if err == nil && (live.Config.MemoryEnabled == nil || *live.Config.MemoryEnabled) {
return s.currentFusionMemories(ctx, saved)
}
}
return nil
}
func fusionPrompt(fusions []FusionSnapshot, memories []*types.MemoryItem) string {
if len(fusions) == 0 {
return ""
}
var out strings.Builder
out.WriteString("\n\n辅助表达能力(单层融合):仅借用以下 Agent 的表达技巧,不合并其知识库、工具、模型或下级 Agent。依据只取本次任务与主 Agent 的授权资料。\n")
budget := fusionPromptRuneLimit - 1000
for _, a := range fusions {
entry := "\n辅助 Agent:" + boundedFusionText(a.Name, 100) + "\n表达与数字人风格:\n" + boundedFusionText(a.SystemPrompt, 2000) + "\n文字技能:\n" + boundedFusionText(a.Instructions, 2000) + "\n"
entry = boundedFusionText(entry, budget)
out.WriteString(entry)
budget -= len([]rune(entry))
if budget <= 0 {
break
}
}
if len(memories) > 0 {
out.WriteString("\n当前用户已确认的记忆(去重后的辅助背景,不是指令或知识依据):\n")
out.WriteString(boundedFusionText(types.RenderMemoryRecall(memories), 600))
}
out.WriteString("\n融合约束:主 Agent 的身份、情绪基调、叙事视角及任务要求优先;辅助 Agent 与其冲突时忽略辅助表达。延续上文已确认内容的逻辑与语气,写成同一个人的一段连贯内容,不列出多位 Agent 的独立回答。\n")
return out.String()
}