package workflow import ( "fmt" "strings" core "git-ssh.u1680832.nyat.app/weichen/Agent-System-Core.git" ) // 声明式工作流与模型生成的工作流用两套上限。 const ( MaxDeclarativeSteps = 20 MaxDynamicSteps = 8 MaxStepRetries = 3 MaxWorkflowRetries = 6 ) // DeniedTools 是工作流步骤和脚本回调都不能调用的工具。 var DeniedTools = map[string]struct{}{ "run_python": {}, "run_shell": {}, "run_workflow": {}, "delegate_task": {}, "squad_orchestrate": {}, "review_findings": {}, } // Limits 区分声明式和动态工作流。 type Limits struct { MaxSteps int AllowScript bool } func DeclarativeLimits() Limits { return Limits{MaxSteps: MaxDeclarativeSteps, AllowScript: true} } func DynamicLimits() Limits { return Limits{MaxSteps: MaxDynamicSteps, AllowScript: false} } var stepKinds = map[string]struct{}{ "prompt": {}, "tool": {}, "hitl": {}, "artifact": {}, "card": {}, "script": {}, } // Validate 检查定义是否能执行。known 报告工具是否已注册;nil 时只检查禁用名单。 func Validate(def core.WorkflowDef, lim Limits, known func(name string) bool) error { if strings.TrimSpace(def.ID) == "" { return fmt.Errorf("workflow: id 为空") } if lim.MaxSteps <= 0 { return fmt.Errorf("workflow: %s: 未设置步骤上限", def.ID) } if n := len(def.Steps); n == 0 || n > lim.MaxSteps { return fmt.Errorf("workflow: %s: 步骤数 %d 超出上限 %d", def.ID, n, lim.MaxSteps) } if def.MaxRetries > MaxWorkflowRetries { return fmt.Errorf("workflow: %s: maxRetries %d 超过 %d", def.ID, def.MaxRetries, MaxWorkflowRetries) } seen := map[string]struct{}{} for _, step := range def.Steps { if strings.TrimSpace(step.ID) == "" { return fmt.Errorf("workflow: %s: 步骤 id 为空", def.ID) } if _, ok := seen[step.ID]; ok { return fmt.Errorf("workflow: %s: 步骤 id %s 重复", def.ID, step.ID) } seen[step.ID] = struct{}{} if _, ok := stepKinds[step.Kind]; !ok { return fmt.Errorf("workflow: %s: 步骤 %s 的 kind %q 不合法", def.ID, step.ID, step.Kind) } if step.Critic != nil && step.Critic.MaxRetries > MaxStepRetries { return fmt.Errorf("workflow: %s: 步骤 %s 的重试上限 %d 超过 %d", def.ID, step.ID, step.Critic.MaxRetries, MaxStepRetries) } switch step.Kind { case "tool": if err := checkTool(def.ID, step, known); err != nil { return err } case "script": if !lim.AllowScript { return fmt.Errorf("workflow: %s: 动态工作流不能包含 script 步骤 %s", def.ID, step.ID) } if strings.TrimSpace(step.Script) == "" || !scriptPathOK(step.Script) { return fmt.Errorf("workflow: %s: 步骤 %s 的脚本路径不合法", def.ID, step.ID) } } } return nil } func checkTool(workflowID string, step core.WorkflowStep, known func(name string) bool) error { name := strings.TrimSpace(step.Tool) if name == "" { return fmt.Errorf("workflow: %s: 步骤 %s 缺少工具名", workflowID, step.ID) } if _, denied := DeniedTools[name]; denied { return fmt.Errorf("workflow: %s: 步骤 %s 不能调用 %s", workflowID, step.ID, name) } if known != nil && !known(name) { return fmt.Errorf("workflow: %s: 步骤 %s 的工具 %s 未注册", workflowID, step.ID, name) } return nil } func scriptPathOK(path string) bool { if path == "" || strings.HasPrefix(path, "/") || strings.Contains(path, "\\") { return false } if path == ".." || strings.HasPrefix(path, "../") || strings.Contains(path, "/../") { return false } return true }