refactor: extraer campo sender en Agent para desacoplar envio de mensajes

Añade un campo `sender effects.MatrixSender` al struct Agent que reemplaza
las llamadas directas a `a.matrix` para enviar mensajes (sendReply, typing,
SendMarkdown en handleTaskEvent). En produccion, sender apunta al mismo
*matrix.Client. Esto permite inyectar un spy en tests sin requerir una
conexion real a Matrix.

El campo `a.matrix` se mantiene para operaciones que no son de envio
(SetPresence, Raw, etc.).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-09 20:13:19 +00:00
parent df7518cf54
commit 2546e43ee2
2 changed files with 10 additions and 8 deletions
+8 -8
View File
@@ -29,8 +29,8 @@ func (a *Agent) handleEvent(ctx context.Context, msgCtx decision.MessageContext,
a.roomCtx.Set(roomID)
if a.cfg.Personality.Behavior.TypingIndicator {
_ = a.matrix.SendTyping(ctx, roomID, true)
defer a.matrix.SendTyping(ctx, roomID, false)
_ = a.sender.SendTyping(ctx, roomID, true)
defer a.sender.SendTyping(ctx, roomID, false)
}
// ── Command flow ─────────────────────────────────────────────────
@@ -233,8 +233,8 @@ func (a *Agent) handleTaskEvent(ctx context.Context, msg bus.AgentMessage) {
a.roomCtx.Set(roomID)
if a.cfg.Personality.Behavior.TypingIndicator {
_ = a.matrix.SendTyping(ctx, roomID, true)
defer a.matrix.SendTyping(ctx, roomID, false)
_ = a.sender.SendTyping(ctx, roomID, true)
defer a.sender.SendTyping(ctx, roomID, false)
}
// Build a synthetic MessageContext from the task
@@ -261,7 +261,7 @@ func (a *Agent) handleTaskEvent(ctx context.Context, msg bus.AgentMessage) {
if rejected {
a.logger.Warn("orchestrated task rejected by sanitizer",
"task_id", task.TaskID, "sender", task.OriginalSender)
_ = a.matrix.SendMarkdown(ctx, roomID, "El mensaje fue rechazado por el filtro de seguridad.")
_ = a.sender.SendMarkdown(ctx, roomID, "El mensaje fue rechazado por el filtro de seguridad.")
return
}
msgCtx.Content = sanitized
@@ -294,7 +294,7 @@ func (a *Agent) handleTaskEvent(ctx context.Context, msg bus.AgentMessage) {
}
// Send reply to Matrix room
if sendErr := a.matrix.SendMarkdown(ctx, roomID, reply); sendErr != nil {
if sendErr := a.sender.SendMarkdown(ctx, roomID, reply); sendErr != nil {
a.logger.Error("failed to send orchestrated reply to Matrix", "err", sendErr)
}
@@ -321,9 +321,9 @@ func (a *Agent) handleTaskEvent(ctx context.Context, msg bus.AgentMessage) {
// If threadID is non-empty, the reply is sent as part of that thread.
func (a *Agent) sendReply(ctx context.Context, roomID, eventID, threadID, markdown string) error {
if threadID != "" {
return a.matrix.SendThreadMarkdown(ctx, roomID, threadID, eventID, markdown)
return a.sender.SendThreadMarkdown(ctx, roomID, threadID, eventID, markdown)
}
return a.matrix.SendReplyMarkdown(ctx, roomID, eventID, markdown)
return a.sender.SendReplyMarkdown(ctx, roomID, eventID, markdown)
}
// parseSeverity converts a config string to sanitize.Severity.
+2
View File
@@ -49,6 +49,7 @@ type Agent struct {
rules []decision.Rule
llm coretypes.CompleteFunc // nil when no LLM configured (simple_bot)
matrix *matrix.Client
sender effects.MatrixSender // used by sendReply; same object as matrix in production
runner *effects.Runner
listener *matrix.Listener
toolReg *tools.Registry
@@ -157,6 +158,7 @@ func New(cfg *config.AgentConfig, rules []decision.Rule, agentACL acl.ACL, logge
rules: rules,
llm: llmFunc,
matrix: matrixClient,
sender: matrixClient,
runner: runner,
toolReg: toolReg,
logger: logger,