From 2457d6c996da7eba9b6b5a7a0e5f99e1062a6964 Mon Sep 17 00:00:00 2001 From: Enmanuel Date: Sat, 7 Mar 2026 01:11:41 +0000 Subject: [PATCH] fix: start.sh siempre recompila el launcher antes de arrancar MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Elimina la condición que solo recompilaba si había cambios en cmd/launcher/. Go compila rápido y esto evita binarios stale cuando hay cambios en pkg/, agents/, tools/, etc. Actualiza también el progreso de la tarea 09. --- .claude/tasks/09-command_system.md | 205 +++++++++++++++++++++++++++++ dev-scripts/server/start.sh | 14 +- 2 files changed, 211 insertions(+), 8 deletions(-) diff --git a/.claude/tasks/09-command_system.md b/.claude/tasks/09-command_system.md index e69de29..24ca232 100644 --- a/.claude/tasks/09-command_system.md +++ b/.claude/tasks/09-command_system.md @@ -0,0 +1,205 @@ +# Task 09 — Sistema de comandos directos (!command) + +## Objetivo + +Implementar un sistema de comandos que permita a los usuarios ejecutar acciones directamente via `!comando` sin depender del LLM. Soportar agentes "simple_bot" que no tienen LLM y solo responden a comandos. + +## Contexto actual + +- `message.Parse` ya detecta `CommandPrefix` (!) y extrae `Command` + `Args` en `MessageContext` +- `decision.MatchCommand()` ya existe para matchear comandos en reglas +- `tools.Registry` ya tiene `Execute(ctx, name, argsJSON)` para ejecutar tools +- Cada agente define sus reglas en `agent.go` con `Rules() []decision.Rule` +- El flujo actual: solo `!help` existe como comando hardcodeado en cada agente + +## Problema + +- Los comandos estan hardcodeados en cada `agent.go` como reglas individuales +- No hay forma de ejecutar tools directamente sin pasar por el LLM +- No hay comandos built-in compartidos entre agentes +- No se puede crear un bot sin LLM (simple_bot) +- El `!help` es estatico y no refleja las tools reales del agente + +## Diseno + +### Arquitectura (pure core / impure shell) + +``` +pkg/command/ -> PURE: tipos Command, parser de args, specs built-in +agents/runtime.go -> composicion: conecta commands con tools y shell +``` + +### Tipos de comandos + +1. **Built-in commands** (disponibles en todos los agentes): + + | Comando | Descripcion | + |------------|----------------------------------------------------| + | `!help` | Lista comandos disponibles (built-in + custom) | + | `!tools` | Lista tools registradas con descripcion | + | `!ping` | Alive check, responde "pong" con timestamp | + | `!status` | Info del agente: uptime, rooms activos, window sizes | + | `!info` | Nombre, version, descripcion del agente | + | `!clear` | Limpia ventana de conversacion del room actual | + | `!version` | Version del agente | + +2. **Tool commands** — ejecutar tools directas: + ``` + !tool -> sin args + !tool key=value -> arg simple + !tool key="valor con espacios" -> arg con espacios + !tool key=value key2=value2 -> multiples args + ``` + Ejemplos: + - `!tool ssh_command host=server1 command="uptime"` + - `!tool current_time` + - `!tool knowledge_search query="como configurar"` + +3. **Custom commands** — definidos por cada agente en su `agent.go` via Rules con MatchCommand (como ahora, pero mejor integrados) + +### Flujo de ejecucion + +``` +Matrix event + -> message.Parse (ya extrae Command + Args) + -> handleEvent: + 1. Si hay Command (empieza con !prefix): + a. Custom command del agente (rules con MatchCommand)? -> ejecutar regla + b. Built-in command? -> ejecutar handler, responder + c. "tool" command? -> parsear args, ejecutar via tools.Registry, responder + d. No encontrado? -> responder "comando desconocido, usa !help" + 2. Si NO es comando: flujo actual (rules -> LLM fallback si hay LLM) + 3. Si NO es comando y NO hay LLM: ignorar (solo responde a comandos) +``` + +**Nota**: las reglas custom del agente tienen prioridad sobre built-ins. Si un agente define una regla `MatchCommand("help")` propia, esa gana sobre el built-in. + +### Nuevo paquete `pkg/command/` (puro) + +```go +// pkg/command/types.go + +// Spec es la spec pura de un comando. Solo datos. +type Spec struct { + Name string + Aliases []string // e.g. ["h"] para help + Description string // descripcion corta para !help + Usage string // e.g. "!tool [key=value ...]" + Hidden bool // no mostrar en !help +} + +// ParsedArgs resultado de parsear "key=value key2=value2" +type ParsedArgs struct { + Positional []string // args sin key= + Named map[string]string // args con key=value + Raw []string // args originales +} +``` + +```go +// pkg/command/parse.go + +// ParseArgs convierte []string{"host=server1", "command=uptime"} en ParsedArgs. Puro. +func ParseArgs(args []string) ParsedArgs { ... } + +// ArgsToJSON convierte ParsedArgs.Named a JSON string para tools.Registry.Execute. Puro. +func ArgsToJSON(named map[string]string) string { ... } +``` + +```go +// pkg/command/builtins.go + +// Builtins retorna las specs de todos los comandos built-in. Puro. +func Builtins() []Spec { ... } +``` + +### Cambios en `agents/runtime.go` + +```go +// CommandHandler ejecuta un comando built-in y devuelve la respuesta texto. +type CommandHandler func(ctx context.Context, msgCtx decision.MessageContext) string + +// Nuevos campos en Agent: +type Agent struct { + // ... existente ... + commands map[string]CommandHandler // built-in command handlers + startTime time.Time // para !status +} +``` + +En `handleEvent`, el flujo cambia a: +```go +// 1. Evaluar reglas custom primero (pueden overridear built-ins) +if msgCtx.Command != "" { + actions := decision.Evaluate(msgCtx, a.rules) + if len(actions) > 0 { + // ejecutar como ahora (expand LLM actions, runner.Execute) + return + } + // 2. Buscar en built-ins + if handler, ok := a.commands[msgCtx.Command]; ok { + reply := handler(ctx, msgCtx) + a.matrix.SendText(ctx, roomID, reply) + return + } + // 3. Comando desconocido + a.matrix.SendText(ctx, roomID, "Comando desconocido. Usa !help") + return +} + +// 4. Sin comando: LLM fallback (si hay LLM) o ignorar +if a.llm == nil { + return // simple_bot: solo responde a comandos +} +// ... flujo LLM actual (DM/mention -> LLM) ... +``` + +### Simple bots (sin LLM) + +Un simple_bot se configura sin seccion `llm` o con `llm.primary.provider: ""`: + +```yaml +agent: + id: monitor-bot + name: Monitor Bot + enabled: true + description: "Bot de monitoreo, solo comandos" + +tools: + ssh: + enabled: true + allowed_targets: ["webserver"] +``` + +En `New()`, si no hay LLM configurado, `a.llm` queda nil. El bot solo responde a comandos. + +## Tareas de implementacion + +### Fase 1 — Core puro (`pkg/command/`) +- [x] Crear `pkg/command/types.go` — tipos Spec, ParsedArgs +- [x] Crear `pkg/command/parse.go` — ParseArgs, ArgsToJSON +- [x] Crear `pkg/command/parse_test.go` — tests del parser +- [x] Crear `pkg/command/builtins.go` — specs de los 7 comandos built-in + BuiltinNames() + +### Fase 2 — Handlers en runtime (`agents/`) +- [x] Agregar campos `commands`, `cmdAliases`, `startTime` al Agent struct +- [x] Implementar handlers: help, tools, ping, info, version, clear, status +- [x] Implementar handler `tool` — parsea args key=value, ejecuta via Registry, formatea respuesta +- [x] Registrar todos los handlers en `New()` via `registerBuiltinCommands()` +- [x] Modificar `handleEvent` — nuevo flujo: rules custom -> built-in -> comando desconocido -> LLM fallback +- [x] Extraer `executeActions()` helper para reutilizar en ambos flujos + +### Fase 3 — Simple bot support +- [x] Hacer LLM opcional en `New()` (no fallar si no hay provider) +- [x] Si `a.llm == nil` y no hay comando, ignorar mensaje +- [ ] Verificar que un agente sin LLM arranca y responde a !help, !tool, !ping + +### Fase 4 — Integracion con agentes existentes +- [x] Eliminar regla `!help` hardcodeada de assistant-bot/agent.go +- [x] Eliminar regla `!help` hardcodeada de asistente-2/agent.go +- [x] Verificar que reglas custom (llm-all, etc.) siguen funcionando (build OK) +- [ ] Test manual: !help, !tools, !tool current_time, !ping, !status, !clear, !info, !version + +### Fase 5 (futura) — Simple bot de ejemplo +- [ ] Crear agente simple_bot de ejemplo sin LLM +- [ ] Documentar patron simple_bot diff --git a/dev-scripts/server/start.sh b/dev-scripts/server/start.sh index 2b34b0a..c7ab928 100755 --- a/dev-scripts/server/start.sh +++ b/dev-scripts/server/start.sh @@ -16,15 +16,13 @@ BIN="$REPO_ROOT/bin/launcher" LOG="$(launcher_log_file)" PID_F="$(launcher_pid_file)" -# Build if needed -if [[ ! -x "$BIN" ]] || [[ "$(find ./cmd/launcher -newer "$BIN" 2>/dev/null | head -1)" ]]; then - info "Ejecutando tests..." - "$GO" test -tags goolm ./... || fail "Tests fallaron — corrige antes de compilar" +# Always rebuild — Go is fast and avoids stale binaries from changes in pkg/, agents/, etc. +info "Ejecutando tests..." +"$GO" test -tags goolm ./... || fail "Tests fallaron — corrige antes de compilar" - info "Compilando launcher..." - mkdir -p "$(dirname "$BIN")" - "$GO" build -tags goolm -o "$BIN" ./cmd/launcher || fail "Error de compilación" -fi +info "Compilando launcher..." +mkdir -p "$(dirname "$BIN")" +"$GO" build -tags goolm -o "$BIN" ./cmd/launcher || fail "Error de compilación" info "Iniciando launcher unificado..."