Files
egutierrez 1b499c9b67 fix: resolver system_prompt_file relativo al directorio del config
Antes, el runtime construia la ruta del system prompt como
agents/<agent-id>/<file>, lo cual fallaba para agentes en
agents/_specials/ (como Father Bot). Ahora:

1. config.Load() guarda el directorio del config en ConfigDir
2. llm.go usa ConfigDir para resolver rutas relativas

Esto corrige que Father Bot operara sin su system prompt completo
(369 lineas de instrucciones, pipeline, seguridad) usando solo la
description de una linea como fallback.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-10 23:21:23 +00:00

105 lines
2.7 KiB
Go

package config
import (
"fmt"
"os"
"path/filepath"
"gopkg.in/yaml.v3"
)
// Load reads and parses an agent config file from the given path.
func Load(path string) (*AgentConfig, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read config %s: %w", path, err)
}
// Expand environment variables in the raw YAML bytes.
expanded := os.ExpandEnv(string(data))
var cfg AgentConfig
if err := yaml.Unmarshal([]byte(expanded), &cfg); err != nil {
return nil, fmt.Errorf("parse config %s: %w", path, err)
}
if err := validate(&cfg); err != nil {
return nil, fmt.Errorf("invalid config %s: %w", path, err)
}
cfg.ConfigDir = filepath.Dir(path)
return &cfg, nil
}
// LoadMeta reads only the `agent:` block from a config file without expanding
// env vars or running full validation. Used by agentctl list to show all
// agents regardless of whether their env vars are configured.
func LoadMeta(path string) (*AgentConfig, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read config %s: %w", path, err)
}
var cfg AgentConfig
if err := yaml.Unmarshal(data, &cfg); err != nil {
return nil, fmt.Errorf("parse config %s: %w", path, err)
}
if cfg.Agent.ID == "" {
return nil, fmt.Errorf("agent.id is required")
}
return &cfg, nil
}
// LoadSpecial reads and parses a special agent config file.
// Special agents have no Matrix identity so validation is lighter.
func LoadSpecial(path string) (*SpecialConfig, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read special config %s: %w", path, err)
}
expanded := os.ExpandEnv(string(data))
var cfg SpecialConfig
if err := yaml.Unmarshal([]byte(expanded), &cfg); err != nil {
return nil, fmt.Errorf("parse special config %s: %w", path, err)
}
if err := validateSpecial(&cfg); err != nil {
return nil, fmt.Errorf("invalid special config %s: %w", path, err)
}
return &cfg, nil
}
// validateSpecial applies sanity checks for special agent configs.
func validateSpecial(cfg *SpecialConfig) error {
if cfg.Special.ID == "" {
return fmt.Errorf("special.id is required")
}
if cfg.Special.Type == "" {
return fmt.Errorf("special.type is required")
}
if cfg.LLM.Primary.Provider == "" {
return fmt.Errorf("llm.primary.provider is required")
}
return nil
}
// validate applies basic sanity checks.
func validate(cfg *AgentConfig) error {
if cfg.Agent.ID == "" {
return fmt.Errorf("agent.id is required")
}
if cfg.Matrix.Homeserver == "" {
return fmt.Errorf("matrix.homeserver is required")
}
if cfg.Matrix.UserID == "" {
return fmt.Errorf("matrix.user_id is required")
}
if cfg.Agent.Type != "robot" && cfg.LLM.Primary.Provider == "" {
return fmt.Errorf("llm.primary.provider is required")
}
return nil
}