feat: launcher descubre agentes en _specials/ con validacion de tipo

El launcher ahora escanea agents/_specials/*/config.yaml ademas de
agents/*/config.yaml para descubrir agentes del sistema con identidad
Matrix (ej: father-bot). Los SpecialConfig ya cargados (orchestrator)
se detectan y saltan via isSpecialConfig() para evitar errores de
validacion.

Tambien actualiza dev-scripts/_common.sh para que config_path_for()
y list_agents_raw() incluyan _specials/ en la busqueda.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-09 22:05:13 +00:00
parent cd22c1c861
commit 2f3f30c86b
2 changed files with 37 additions and 2 deletions
+33
View File
@@ -34,6 +34,7 @@ import (
_ "github.com/enmanuel/agents/agents/asistente-2" _ "github.com/enmanuel/agents/agents/asistente-2"
_ "github.com/enmanuel/agents/agents/meteorologo" _ "github.com/enmanuel/agents/agents/meteorologo"
_ "github.com/enmanuel/agents/agents/test-personality" _ "github.com/enmanuel/agents/agents/test-personality"
_ "github.com/enmanuel/agents/agents/_specials/father-bot"
testbot "github.com/enmanuel/agents/agents/test-bot" testbot "github.com/enmanuel/agents/agents/test-bot"
) )
@@ -51,6 +52,10 @@ func main() {
if len(configPaths) == 0 { if len(configPaths) == 0 {
matches, _ := filepath.Glob("agents/*/config.yaml") matches, _ := filepath.Glob("agents/*/config.yaml")
configPaths = matches configPaths = matches
// Also discover agent-type specials (e.g. father-bot).
// SpecialConfig middleware (orchestrator) is handled separately.
specials, _ := filepath.Glob("agents/_specials/*/config.yaml")
configPaths = append(configPaths, specials...)
} }
return nil return nil
}, },
@@ -143,9 +148,22 @@ func main() {
}() }()
// ── Start normal agents ── // ── Start normal agents ──
// Build a set of special IDs already loaded (e.g. orchestrator)
// so the discovery loop skips them instead of failing on validation.
loadedSpecials := make(map[string]bool)
if orch != nil {
loadedSpecials[orch.cfg.Special.ID] = true
}
var scannerOnce scanOnce var scannerOnce scanOnce
for _, path := range configPaths { for _, path := range configPaths {
path := path path := path
// Skip configs that belong to already-loaded specials.
if isSpecialConfig(path, loadedSpecials) {
continue
}
cfg, err := config.Load(path) cfg, err := config.Load(path)
if err != nil { if err != nil {
logger.Error("failed to load config", "path", path, "err", err) logger.Error("failed to load config", "path", path, "err", err)
@@ -337,3 +355,18 @@ func parseLogLevel(level string) slog.Level {
func newLogger(level string) *slog.Logger { func newLogger(level string) *slog.Logger {
return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: parseLogLevel(level)})) return slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: parseLogLevel(level)}))
} }
// isSpecialConfig checks whether a config path belongs to a special agent
// that was already loaded (e.g. orchestrator). It reads the YAML to detect
// a "special:" top-level key. This avoids config.Load() failing with
// validation errors for SpecialConfig files.
func isSpecialConfig(path string, loadedSpecials map[string]bool) bool {
if len(loadedSpecials) == 0 {
return false
}
cfg, err := config.LoadSpecial(path)
if err != nil {
return false // not a valid special config → let Load() handle it
}
return loadedSpecials[cfg.Special.ID]
}
+4 -2
View File
@@ -51,9 +51,10 @@ read_pid() {
} }
# Map agent ID to its config path by scanning agent directories. # Map agent ID to its config path by scanning agent directories.
# Also scans agents/_specials/ for privileged system agents (e.g. father-bot).
config_path_for() { config_path_for() {
local target_id="$1" local target_id="$1"
for cfg in agents/*/config.yaml; do for cfg in agents/*/config.yaml agents/_specials/*/config.yaml; do
[[ -f "$cfg" ]] || continue [[ -f "$cfg" ]] || continue
local id local id
id=$(grep -m1 '^ id:' "$cfg" | awk '{print $2}') id=$(grep -m1 '^ id:' "$cfg" | awk '{print $2}')
@@ -150,8 +151,9 @@ is_launcher_running() {
# ── Agent discovery ──────────────────────────────────────────────────────── # ── Agent discovery ────────────────────────────────────────────────────────
# Prints: id|version|enabled|description (one line per agent) # Prints: id|version|enabled|description (one line per agent)
# Also scans agents/_specials/ for privileged system agents.
list_agents_raw() { list_agents_raw() {
for cfg in agents/*/config.yaml; do for cfg in agents/*/config.yaml agents/_specials/*/config.yaml; do
[[ -f "$cfg" ]] || continue [[ -f "$cfg" ]] || continue
local id version enabled desc local id version enabled desc
id=$(grep -m1 '^ id:' "$cfg" | awk '{print $2}') id=$(grep -m1 '^ id:' "$cfg" | awk '{print $2}')