fc644ecd6e
Reemplaza el scaffold del echobot por la plataforma completa de bots traida desde ~/DataProyects/Github/agents_and_robots tras la operacion Matrix-out: los bots ya no hablan por Matrix sino por el bus unibus (modelo todo-rooms + E2E via shell/transportunibus sobre github.com/enmanuel/unibus/pkg/client). - go.mod: replace de unibus -> ../unibus y de fn-registry -> ../../../.. (paths relativos reajustados a la nueva ubicacion dentro de fn_registry). - app.md: bump a 0.2.0, descripcion + arquitectura + comandos + gotchas reales. - modulo Go conservado como github.com/enmanuel/agents (sin reescribir imports). agents_and_robots queda archivado como museo de la era Matrix.
206 lines
7.5 KiB
Markdown
206 lines
7.5 KiB
Markdown
# 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 <nombre> -> sin args
|
|
!tool <nombre> key=value -> arg simple
|
|
!tool <nombre> key="valor con espacios" -> arg con espacios
|
|
!tool <nombre> 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 <name> [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
|