e481cb8783
Implementa issue 0025: catálogo central de automatizaciones cron y scaffolder.
- crons/: directorio de automatizaciones nombradas con README explicando la
convención. Incluye dos ejemplos listos para usar:
· good-morning (send_message, 0 9 * * *) — saludo diario
· daily-summary (llm_prompt, 0 18 * * *) — resumen generado por LLM
- dev-scripts/cron/new.sh: scaffolder interactivo — pregunta nombre,
descripción, tipo de acción y cron expression; crea schedule.yaml +
archivo de prompt vacío; imprime el bloque YAML para copiar en config.yaml.
- dev-scripts/cron/list.sh: lista todas las automatizaciones del catálogo
con nombre, tipo, cron y descripción en formato tabular.
- dev-scripts/cron/apply.sh: añade la automatización al config.yaml del
agente indicado usando yq si está disponible; si no, imprime el bloque
YAML para copiar a mano (sin dependencias obligatorias).
- shell/cron/scheduler.go: exporta Fire(ctx, sc) para disparo inmediato
de un schedule sin esperar al timer cron — útil en tests y CLI.
- shell/cron/scheduler_test.go: cuatro tests nuevos para Fire()
(send_message inline, llm_prompt, sin output_room, sin LLM).
TestScheduler_SkipsInvalidSchedule y TestFire_LLMPrompt_NoLLM_Skips
reemplazados por versiones instantáneas usando Fire en lugar de
@every 100ms + sleep, eliminando ~700ms de tiempo de test.
110 lines
2.9 KiB
Go
110 lines
2.9 KiB
Go
// Package cron provides a scheduler for autonomous bot activity.
|
|
// It is part of the impure shell: it reads files, calls LLMs, and sends Matrix messages.
|
|
package cron
|
|
|
|
import (
|
|
"context"
|
|
"log/slog"
|
|
|
|
"github.com/robfig/cron/v3"
|
|
|
|
"github.com/enmanuel/agents/internal/config"
|
|
coretypes "github.com/enmanuel/agents/pkg/llm"
|
|
)
|
|
|
|
// MatrixSender is the subset of matrix.Client needed by the scheduler.
|
|
type MatrixSender interface {
|
|
SendMarkdown(ctx context.Context, roomID, markdown string) error
|
|
}
|
|
|
|
// Scheduler fires configured schedules and executes send_message or llm_prompt actions.
|
|
type Scheduler struct {
|
|
cfg []config.ScheduleCfg
|
|
matrix MatrixSender
|
|
llm coretypes.CompleteFunc // nil when agent has no LLM
|
|
model string
|
|
logger *slog.Logger
|
|
cron *cron.Cron
|
|
}
|
|
|
|
// New creates a Scheduler. llm and model are optional (nil/empty for agents without LLM).
|
|
func New(
|
|
cfg []config.ScheduleCfg,
|
|
matrix MatrixSender,
|
|
llm coretypes.CompleteFunc,
|
|
model string,
|
|
logger *slog.Logger,
|
|
) *Scheduler {
|
|
return &Scheduler{
|
|
cfg: cfg,
|
|
matrix: matrix,
|
|
llm: llm,
|
|
model: model,
|
|
logger: logger.With("component", "cron"),
|
|
cron: cron.New(),
|
|
}
|
|
}
|
|
|
|
// Fire immediately executes the action for the given schedule, bypassing the cron timer.
|
|
// Useful for tests and manual triggering from CLI.
|
|
func (s *Scheduler) Fire(ctx context.Context, sc config.ScheduleCfg) {
|
|
room := sc.OutputRoom
|
|
if room == "" {
|
|
s.logger.Warn("Fire: schedule has no output_room, skipping", "name", sc.Name)
|
|
return
|
|
}
|
|
handler := s.buildHandler(sc)
|
|
if handler == nil {
|
|
s.logger.Warn("Fire: unsupported action kind", "name", sc.Name, "kind", sc.Action.Kind)
|
|
return
|
|
}
|
|
handler(ctx, room)
|
|
}
|
|
|
|
// Start registers all schedules and starts the cron loop.
|
|
// It returns when ctx is cancelled, stopping the cron runner.
|
|
func (s *Scheduler) Start(ctx context.Context) {
|
|
for _, sc := range s.cfg {
|
|
sc := sc // capture range var
|
|
if sc.Cron == "" || sc.Action.Kind == "" {
|
|
s.logger.Warn("skipping invalid schedule", "name", sc.Name, "cron", sc.Cron, "kind", sc.Action.Kind)
|
|
continue
|
|
}
|
|
|
|
room := sc.OutputRoom
|
|
if room == "" {
|
|
s.logger.Warn("schedule has no output_room, skipping", "name", sc.Name)
|
|
continue
|
|
}
|
|
|
|
handler := s.buildHandler(sc)
|
|
if handler == nil {
|
|
s.logger.Warn("unsupported action kind, skipping", "name", sc.Name, "kind", sc.Action.Kind)
|
|
continue
|
|
}
|
|
|
|
_, err := s.cron.AddFunc(sc.Cron, func() {
|
|
handler(ctx, room)
|
|
})
|
|
if err != nil {
|
|
s.logger.Error("failed to register schedule",
|
|
"name", sc.Name,
|
|
"cron", sc.Cron,
|
|
"err", err,
|
|
)
|
|
continue
|
|
}
|
|
|
|
s.logger.Info("schedule registered", "name", sc.Name, "cron", sc.Cron, "kind", sc.Action.Kind, "room", room)
|
|
}
|
|
|
|
s.cron.Start()
|
|
s.logger.Info("cron scheduler started", "schedules", len(s.cfg))
|
|
|
|
<-ctx.Done()
|
|
s.logger.Info("cron scheduler stopping")
|
|
cronCtx := s.cron.Stop()
|
|
<-cronCtx.Done()
|
|
s.logger.Info("cron scheduler stopped")
|
|
}
|