4b72b5ab28
Añade RegisterCommand(spec, handler) al Agent para que cada agente pueda registrar comandos propios (!xxx) sin modificar built-ins ni usar reglas. Cambios principales: - agents/runtime.go: nuevo método RegisterCommand + campo customSpecs - agents/commands.go: !help muestra comandos del agente en sección separada, extrae writeSpec helper, elimina customCommandRules (ya no se usan reglas para comandos) - handleEvent simplificado: los comandos se resuelven por lookup directo (built-in + registered), nunca pasan por reglas ni LLM Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
181 lines
5.0 KiB
Go
181 lines
5.0 KiB
Go
package agents
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/enmanuel/agents/pkg/command"
|
|
"github.com/enmanuel/agents/pkg/decision"
|
|
)
|
|
|
|
// registerBuiltinCommands registers all built-in command handlers.
|
|
func (a *Agent) registerBuiltinCommands() {
|
|
a.commands["help"] = a.cmdHelp
|
|
a.commands["tools"] = a.cmdTools
|
|
a.commands["tool"] = a.cmdTool
|
|
a.commands["ping"] = a.cmdPing
|
|
a.commands["status"] = a.cmdStatus
|
|
a.commands["info"] = a.cmdInfo
|
|
a.commands["clear"] = a.cmdClear
|
|
a.commands["version"] = a.cmdVersion
|
|
}
|
|
|
|
// cmdHelp lists all available commands (built-in + agent-specific).
|
|
func (a *Agent) cmdHelp(_ context.Context, _ decision.MessageContext) string {
|
|
var b strings.Builder
|
|
b.WriteString("Comandos disponibles:\n\n")
|
|
|
|
// Built-in commands
|
|
for _, spec := range command.Builtins() {
|
|
if spec.Hidden {
|
|
continue
|
|
}
|
|
writeSpec(&b, spec)
|
|
}
|
|
|
|
// Agent-specific commands (registered via RegisterCommand)
|
|
if len(a.customSpecs) > 0 {
|
|
b.WriteString("\nComandos del agente:\n")
|
|
for _, spec := range a.customSpecs {
|
|
if spec.Hidden {
|
|
continue
|
|
}
|
|
writeSpec(&b, spec)
|
|
}
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
// writeSpec formats a single command spec for the help output.
|
|
func writeSpec(b *strings.Builder, spec command.Spec) {
|
|
aliases := ""
|
|
if len(spec.Aliases) > 0 {
|
|
aliases = " (" + strings.Join(prefixAll(spec.Aliases, "!"), ", ") + ")"
|
|
}
|
|
usage := spec.Usage
|
|
if usage == "" {
|
|
usage = "!" + spec.Name
|
|
}
|
|
fmt.Fprintf(b, " %s%s — %s\n", usage, aliases, spec.Description)
|
|
}
|
|
|
|
// cmdTools lists all tools registered in the agent's tool registry.
|
|
func (a *Agent) cmdTools(_ context.Context, _ decision.MessageContext) string {
|
|
names := a.toolReg.Names()
|
|
if len(names) == 0 {
|
|
return "No hay tools registradas."
|
|
}
|
|
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "Tools disponibles (%d):\n\n", len(names))
|
|
for _, name := range names {
|
|
t, _ := a.toolReg.Get(name)
|
|
fmt.Fprintf(&b, " %s — %s\n", t.Def.Name, t.Def.Description)
|
|
for _, p := range t.Def.Parameters {
|
|
req := ""
|
|
if p.Required {
|
|
req = " (requerido)"
|
|
}
|
|
fmt.Fprintf(&b, " %s: %s%s\n", p.Name, p.Description, req)
|
|
}
|
|
}
|
|
b.WriteString("\nUso: !tool <nombre> [key=value ...]")
|
|
return b.String()
|
|
}
|
|
|
|
// cmdTool executes a tool directly with key=value args.
|
|
func (a *Agent) cmdTool(ctx context.Context, msgCtx decision.MessageContext) string {
|
|
if len(msgCtx.Args) == 0 {
|
|
return "Uso: !tool <nombre> [key=value ...]\nUsa !tools para ver tools disponibles."
|
|
}
|
|
|
|
toolName := msgCtx.Args[0]
|
|
if _, ok := a.toolReg.Get(toolName); !ok {
|
|
return fmt.Sprintf("Tool %q no encontrada. Usa !tools para ver tools disponibles.", toolName)
|
|
}
|
|
|
|
// Parse remaining args as key=value
|
|
parsed := command.ParseArgs(msgCtx.Args[1:])
|
|
argsJSON := command.ArgsToJSON(parsed.Named)
|
|
|
|
a.logger.Info("executing tool via command",
|
|
"tool", toolName,
|
|
"args", argsJSON,
|
|
)
|
|
|
|
result := a.toolReg.Execute(ctx, toolName, argsJSON)
|
|
if result.Err != nil {
|
|
return fmt.Sprintf("Error ejecutando %s: %s", toolName, result.Err)
|
|
}
|
|
|
|
return fmt.Sprintf("%s:\n%s", toolName, result.Output)
|
|
}
|
|
|
|
// cmdPing responds with pong and timestamp.
|
|
func (a *Agent) cmdPing(_ context.Context, _ decision.MessageContext) string {
|
|
return fmt.Sprintf("pong — %s", time.Now().Format(time.RFC3339))
|
|
}
|
|
|
|
// cmdStatus shows agent uptime and active rooms.
|
|
func (a *Agent) cmdStatus(_ context.Context, _ decision.MessageContext) string {
|
|
uptime := time.Since(a.startTime).Truncate(time.Second)
|
|
|
|
a.windowsMu.RLock()
|
|
roomCount := len(a.windows)
|
|
a.windowsMu.RUnlock()
|
|
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "Estado de %s:\n", a.cfg.Agent.Name)
|
|
fmt.Fprintf(&b, " Uptime: %s\n", uptime)
|
|
fmt.Fprintf(&b, " Rooms activos: %d\n", roomCount)
|
|
fmt.Fprintf(&b, " Window size: %d\n", a.windowSize)
|
|
fmt.Fprintf(&b, " Tools: %d\n", a.toolReg.Len())
|
|
|
|
if a.llm != nil {
|
|
fmt.Fprintf(&b, " LLM: %s/%s\n", a.cfg.LLM.Primary.Provider, a.cfg.LLM.Primary.Model)
|
|
} else {
|
|
b.WriteString(" LLM: no configurado\n")
|
|
}
|
|
|
|
return b.String()
|
|
}
|
|
|
|
// cmdInfo shows agent name, version, and description.
|
|
func (a *Agent) cmdInfo(_ context.Context, _ decision.MessageContext) string {
|
|
var b strings.Builder
|
|
fmt.Fprintf(&b, "Nombre: %s\n", a.cfg.Agent.Name)
|
|
fmt.Fprintf(&b, "ID: %s\n", a.cfg.Agent.ID)
|
|
if a.cfg.Agent.Version != "" {
|
|
fmt.Fprintf(&b, "Version: %s\n", a.cfg.Agent.Version)
|
|
}
|
|
fmt.Fprintf(&b, "Descripcion: %s\n", a.cfg.Agent.Description)
|
|
return b.String()
|
|
}
|
|
|
|
// cmdClear clears the conversation window for the current room.
|
|
func (a *Agent) cmdClear(_ context.Context, msgCtx decision.MessageContext) string {
|
|
a.ClearWindow(msgCtx.RoomID)
|
|
return "Ventana de conversacion limpiada."
|
|
}
|
|
|
|
// cmdVersion shows the agent version.
|
|
func (a *Agent) cmdVersion(_ context.Context, _ decision.MessageContext) string {
|
|
v := a.cfg.Agent.Version
|
|
if v == "" {
|
|
v = "sin version"
|
|
}
|
|
return fmt.Sprintf("%s %s", a.cfg.Agent.Name, v)
|
|
}
|
|
|
|
// prefixAll adds a prefix to each string in a slice.
|
|
func prefixAll(ss []string, prefix string) []string {
|
|
out := make([]string, len(ss))
|
|
for i, s := range ss {
|
|
out[i] = prefix + s
|
|
}
|
|
return out
|
|
}
|