Files
unibots/shell/cron/actions.go
T
agent fc644ecd6e feat: import agents_and_robots platform as unibots (Matrix-out, unibus transport)
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.
2026-06-07 11:50:13 +02:00

117 lines
3.3 KiB
Go

package cron
import (
"context"
"fmt"
"os"
"strings"
"github.com/enmanuel/agents/internal/config"
coretypes "github.com/enmanuel/agents/pkg/llm"
)
const actionKindSendMessage = "send_message"
const actionKindLLMPrompt = "llm_prompt"
// handler is a function that fires when a schedule triggers.
type handler func(ctx context.Context, room string)
// buildHandler returns the handler for a schedule, or nil for unsupported kinds.
func (s *Scheduler) buildHandler(sc config.ScheduleCfg) handler {
switch sc.Action.Kind {
case actionKindSendMessage:
return s.sendMessageHandler(sc)
case actionKindLLMPrompt:
return s.llmPromptHandler(sc)
default:
return nil
}
}
// sendMessageHandler returns a handler that sends a static message to a Matrix room.
// The message content is resolved in priority order: Message > Template file.
func (s *Scheduler) sendMessageHandler(sc config.ScheduleCfg) handler {
return func(ctx context.Context, room string) {
content, err := resolveContent(sc.Action.Message, sc.Action.Template)
if err != nil {
s.logger.Error("send_message: failed to resolve content",
"name", sc.Name, "err", err)
return
}
if content == "" {
s.logger.Warn("send_message: empty content, skipping", "name", sc.Name)
return
}
s.logger.Info("cron_fire", "name", sc.Name, "kind", actionKindSendMessage, "room", room)
if err := s.sender.SendMarkdown(ctx, room, content); err != nil {
s.logger.Error("send_message: bus send failed",
"name", sc.Name, "room", room, "err", err)
}
}
}
// llmPromptHandler returns a handler that calls the LLM with a prompt and sends
// the response to a Matrix room.
func (s *Scheduler) llmPromptHandler(sc config.ScheduleCfg) handler {
return func(ctx context.Context, room string) {
if s.llm == nil {
s.logger.Warn("llm_prompt: no LLM configured, skipping", "name", sc.Name)
return
}
prompt, err := resolveContent(sc.Action.Prompt, sc.Action.Template)
if err != nil {
s.logger.Error("llm_prompt: failed to resolve prompt",
"name", sc.Name, "err", err)
return
}
if prompt == "" {
s.logger.Warn("llm_prompt: empty prompt, skipping", "name", sc.Name)
return
}
s.logger.Info("cron_fire", "name", sc.Name, "kind", actionKindLLMPrompt, "room", room)
req := coretypes.CompletionRequest{
Model: s.model,
Messages: []coretypes.Message{
{Role: coretypes.RoleUser, Content: prompt},
},
}
resp, err := s.llm(ctx, req)
if err != nil {
s.logger.Error("llm_prompt: LLM call failed",
"name", sc.Name, "err", err)
return
}
content := strings.TrimSpace(resp.Content)
if content == "" {
s.logger.Warn("llm_prompt: LLM returned empty response", "name", sc.Name)
return
}
if err := s.sender.SendMarkdown(ctx, room, content); err != nil {
s.logger.Error("llm_prompt: bus send failed",
"name", sc.Name, "room", room, "err", err)
}
}
}
// resolveContent returns the inline text if non-empty, otherwise reads the file at templatePath.
func resolveContent(inline, templatePath string) (string, error) {
if inline != "" {
return inline, nil
}
if templatePath == "" {
return "", nil
}
data, err := os.ReadFile(templatePath)
if err != nil {
return "", fmt.Errorf("reading template %q: %w", templatePath, err)
}
return strings.TrimSpace(string(data)), nil
}