// agent_loader_test.go 测试 AgentDef 文件加载和 YAML frontmatter 解析. package engine import ( "os" "path/filepath" "testing" ) // --- parseAgentDefFrontmatter --- func TestParseAgentDefFrontmatter_BasicFields(t *testing.T) { fm := `agent_type: TestAgent description: A test agent when_to_use: Use when testing model: claude-haiku-4-5 max_turns: 15 background: true` def := &AgentDefinition{} parseAgentDefFrontmatter(fm, def) if def.AgentType != "TestAgent" { t.Errorf("AgentType: got %q", def.AgentType) } if def.Description != "A test agent" { t.Errorf("Description: got %q", def.Description) } if def.WhenToUse != "Use when testing" { t.Errorf("WhenToUse: got %q", def.WhenToUse) } if def.Model != "claude-haiku-4-5" { t.Errorf("Model: got %q", def.Model) } if def.MaxTurns != 15 { t.Errorf("MaxTurns: want 15, got %d", def.MaxTurns) } if !def.Background { t.Error("Background: want true, got false") } } func TestParseAgentDefFrontmatter_BackgroundFalse(t *testing.T) { fm := `agent_type: Test background: false` def := &AgentDefinition{} parseAgentDefFrontmatter(fm, def) if def.Background { t.Error("background: false should result in Background=false") } } func TestParseAgentDefFrontmatter_BackgroundYes(t *testing.T) { fm := `agent_type: Test background: yes` def := &AgentDefinition{} parseAgentDefFrontmatter(fm, def) if !def.Background { t.Error("background: yes should result in Background=true") } } func TestParseAgentDefFrontmatter_ListFormat(t *testing.T) { fm := `agent_type: ReadOnly allowed_tools: - Read - Grep - Glob disallowed_tools: - Write - Edit background_allowed_tools: - Grep allowed_sub_agent_types: - Explore - Plan` def := &AgentDefinition{} parseAgentDefFrontmatter(fm, def) if len(def.AllowedTools) != 3 { t.Errorf("AllowedTools: want 3, got %d: %v", len(def.AllowedTools), def.AllowedTools) } if len(def.DisallowedTools) != 2 { t.Errorf("DisallowedTools: want 2, got %d", len(def.DisallowedTools)) } if len(def.BackgroundAllowedTools) != 1 || def.BackgroundAllowedTools[0] != "Grep" { t.Errorf("BackgroundAllowedTools: want [Grep], got %v", def.BackgroundAllowedTools) } if len(def.AllowedSubAgentTypes) != 2 { t.Errorf("AllowedSubAgentTypes: want 2, got %v", def.AllowedSubAgentTypes) } } func TestParseAgentDefFrontmatter_CommaSeparatedFormat(t *testing.T) { fm := `agent_type: Compact allowed_tools: Read, Grep, Glob disallowed_tools: Write, Edit allowed_sub_agent_types: Explore, Plan` def := &AgentDefinition{} parseAgentDefFrontmatter(fm, def) if len(def.AllowedTools) != 3 { t.Errorf("AllowedTools: want 3, got %v", def.AllowedTools) } if len(def.DisallowedTools) != 2 { t.Errorf("DisallowedTools: want 2, got %v", def.DisallowedTools) } if len(def.AllowedSubAgentTypes) != 2 { t.Errorf("AllowedSubAgentTypes: want 2, got %v", def.AllowedSubAgentTypes) } } func TestParseAgentDefFrontmatter_EmptyFrontmatter(t *testing.T) { def := &AgentDefinition{} parseAgentDefFrontmatter("", def) // 所有字段保持零值 if def.AgentType != "" || def.Background || len(def.AllowedTools) > 0 { t.Error("empty frontmatter should leave all fields at zero values") } } // --- LoadAgentDefFile --- func TestLoadAgentDefFile_FullFile(t *testing.T) { content := `--- agent_type: WMSAudit description: Audits WMS operations when_to_use: Use when you need to verify warehouse data allowed_tools: - Read - Grep background: true background_allowed_tools: - Grep --- Additional context about WMS auditing.` tmp := t.TempDir() path := filepath.Join(tmp, "wms_audit.md") if err := os.WriteFile(path, []byte(content), 0644); err != nil { t.Fatal(err) } def, err := LoadAgentDefFile(path) if err != nil { t.Fatalf("LoadAgentDefFile: %v", err) } if def.AgentType != "WMSAudit" { t.Errorf("AgentType: got %q", def.AgentType) } if !def.Background { t.Error("Background: want true") } if len(def.AllowedTools) != 2 { t.Errorf("AllowedTools: want 2, got %v", def.AllowedTools) } if len(def.BackgroundAllowedTools) != 1 { t.Errorf("BackgroundAllowedTools: want 1, got %v", def.BackgroundAllowedTools) } } func TestLoadAgentDefFile_NoFrontmatter_BodyBecomesWhenToUse(t *testing.T) { // 无 frontmatter 时正文作为 WhenToUse content := "Use this agent for audit tasks." tmp := t.TempDir() path := filepath.Join(tmp, "audit.md") _ = os.WriteFile(path, []byte(content), 0644) def, err := LoadAgentDefFile(path) if err != nil { t.Fatalf("LoadAgentDefFile: %v", err) } if def.WhenToUse != "Use this agent for audit tasks." { t.Errorf("WhenToUse from body: got %q", def.WhenToUse) } } func TestLoadAgentDefFile_NotFound(t *testing.T) { _, err := LoadAgentDefFile("/nonexistent/path/agent.md") if err == nil { t.Error("expected error for nonexistent file") } } // --- ScanAgentDefsDir --- func TestScanAgentDefsDir_EmptyDir(t *testing.T) { tmp := t.TempDir() defs, err := ScanAgentDefsDir(tmp) if err != nil { t.Fatalf("unexpected error: %v", err) } if len(defs) != 0 { t.Errorf("expected 0 defs from empty dir, got %d", len(defs)) } } func TestScanAgentDefsDir_NotExist(t *testing.T) { defs, err := ScanAgentDefsDir("/nonexistent/path/agents") if err != nil { t.Fatalf("nonexistent dir should not error: %v", err) } if len(defs) != 0 { t.Errorf("expected 0 defs, got %d", len(defs)) } } func TestScanAgentDefsDir_FlatFormat(t *testing.T) { tmp := t.TempDir() // 两个扁平 .md 文件 _ = os.WriteFile(filepath.Join(tmp, "explore.md"), []byte(`--- agent_type: Explore description: Explore agent ---`), 0644) _ = os.WriteFile(filepath.Join(tmp, "plan.md"), []byte(`--- agent_type: Plan description: Plan agent ---`), 0644) // 非 .md 文件应被忽略 _ = os.WriteFile(filepath.Join(tmp, "readme.txt"), []byte("ignore me"), 0644) defs, err := ScanAgentDefsDir(tmp) if err != nil { t.Fatalf("ScanAgentDefsDir: %v", err) } if len(defs) != 2 { t.Errorf("expected 2 defs, got %d", len(defs)) } } func TestScanAgentDefsDir_SubdirFormat(t *testing.T) { tmp := t.TempDir() // 子目录格式:agents/explore/AGENT.md exploreDir := filepath.Join(tmp, "explore") _ = os.MkdirAll(exploreDir, 0755) _ = os.WriteFile(filepath.Join(exploreDir, "AGENT.md"), []byte(`--- agent_type: Explore description: Explore via subdir ---`), 0644) defs, err := ScanAgentDefsDir(tmp) if err != nil { t.Fatalf("ScanAgentDefsDir: %v", err) } if len(defs) != 1 || defs[0].AgentType != "Explore" { t.Errorf("expected 1 Explore def, got %v", defs) } } func TestScanAgentDefsDir_SubdirOverridesFlatFormat(t *testing.T) { // 同名时子目录格式优先 tmp := t.TempDir() // 扁平格式(旧) _ = os.WriteFile(filepath.Join(tmp, "audit.md"), []byte(`--- agent_type: Audit description: Flat version ---`), 0644) // 子目录格式(新,应优先) auditDir := filepath.Join(tmp, "audit") _ = os.MkdirAll(auditDir, 0755) _ = os.WriteFile(filepath.Join(auditDir, "AGENT.md"), []byte(`--- agent_type: Audit description: Subdir version ---`), 0644) defs, err := ScanAgentDefsDir(tmp) if err != nil { t.Fatalf("ScanAgentDefsDir: %v", err) } if len(defs) != 1 { t.Errorf("expected 1 def (deduped), got %d", len(defs)) } if defs[0].Description != "Subdir version" { t.Errorf("subdir should override flat: got description %q", defs[0].Description) } } func TestScanAgentDefsDir_AgentTypeFromFilename(t *testing.T) { // 如果 frontmatter 没有 agent_type,用文件名 tmp := t.TempDir() _ = os.WriteFile(filepath.Join(tmp, "warehouse-audit.md"), []byte(`--- description: A warehouse agent ---`), 0644) defs, err := ScanAgentDefsDir(tmp) if err != nil { t.Fatalf("ScanAgentDefsDir: %v", err) } if len(defs) != 1 || defs[0].AgentType != "warehouse-audit" { t.Errorf("expected AgentType=warehouse-audit from filename, got %v", defs) } } // --- FileAgentDefLoader --- func TestFileAgentDefLoader_LoadAgentDefs(t *testing.T) { tmp := t.TempDir() _ = os.WriteFile(filepath.Join(tmp, "myagent.md"), []byte(`--- agent_type: MyAgent description: Test ---`), 0644) loader := &FileAgentDefLoader{Dir: tmp} defs, err := loader.LoadAgentDefs() if err != nil { t.Fatalf("LoadAgentDefs: %v", err) } if len(defs) != 1 || defs[0].AgentType != "MyAgent" { t.Errorf("expected [MyAgent], got %v", defs) } } // --- AgentRegistry 与 AgentDefLoader 集成 --- func TestAgentDefLoader_RegisterAll(t *testing.T) { tmp := t.TempDir() _ = os.WriteFile(filepath.Join(tmp, "custom.md"), []byte(`--- agent_type: CustomAudit description: Custom audit agent allowed_tools: - Read - Grep background: true allowed_sub_agent_types: - Explore ---`), 0644) loader := &FileAgentDefLoader{Dir: tmp} defs, err := loader.LoadAgentDefs() if err != nil { t.Fatal(err) } r := NewAgentRegistry() for _, def := range defs { if err := r.Register(def); err != nil { t.Fatalf("Register(%q): %v", def.AgentType, err) } } def, ok := r.Get("CustomAudit") if !ok { t.Fatal("CustomAudit not registered") } if !def.Background { t.Error("Background should be true") } if len(def.AllowedSubAgentTypes) != 1 || def.AllowedSubAgentTypes[0] != "Explore" { t.Errorf("AllowedSubAgentTypes: want [Explore], got %v", def.AllowedSubAgentTypes) } }