Se estandariza la numeración de todos los issues de 3 dígitos a 4 dígitos (e.g. 005 → 0005, 010 → 0010) para mantener consistencia con la convención definida en create_issue.md. Se actualiza el README con los nuevos nombres y links. No hay cambios de contenido en los issues, solo renombrado. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
10 KiB
015 — Soporte multi-plataforma: Telegram como segunda plataforma
Objetivo
Desacoplar el runtime de agentes de Matrix e introducir abstracciones de plataforma que permitan conectar un mismo agente a multiples servicios de mensajeria. Implementar Telegram como primera plataforma adicional para validar el diseno.
Contexto
- Actualmente
agents/runtime.godepende directamente de*matrix.Clienty*matrix.Listener shell/effects/runner.goya defineMatrixSendercomo interfaz, pero con nombre acopladodecision.MessageContextes generico — no tiene nada de Matrix- Las reglas, LLM, tools y memoria son independientes de la plataforma
- El acoplamiento esta en: runtime.go, effects/runner.go, listener, y algunos tools (matrix_send)
Prerequisitos
- Ninguno estricto. Se puede hacer de forma incremental sin romper Matrix.
Tareas
Fase 1: Abstracciones de plataforma en pkg/platform/
-
1.1 Crear
pkg/platform/types.gocon las interfaces puras:// Messenger envía mensajes a una plataforma de chat. type Messenger interface { SendText(ctx context.Context, roomID, text string) error SendMarkdown(ctx context.Context, roomID, markdown string) error SendReplyMarkdown(ctx context.Context, roomID, inReplyTo, markdown string) error SendTyping(ctx context.Context, roomID string, typing bool) error } // EventSource escucha eventos de una plataforma y los entrega como MessageContext. type EventSource interface { Run(ctx context.Context) error } // Platform agrupa Messenger + EventSource para una plataforma concreta. type Platform interface { Messenger EventSource Name() string // "matrix", "telegram", "slack", etc. }Nota: estas interfaces van en
pkg/porque son tipos puros (no ejecutan I/O, solo los definen). -
1.2 Definir
PlatformIDcomo prefijo para room IDs multi-plataforma:- Formato:
matrix:!abc123:server.com,telegram:chat_456 - Crear helpers
PrefixRoomID(platform, rawID) stringyParseRoomID(prefixed) (platform, rawID) - Esto permite que la memoria y windows no mezclen contextos entre plataformas
- Formato:
Fase 2: Adaptar shell/matrix/ a las interfaces
-
2.1 Verificar que
shell/matrix/Clientya satisfaceplatform.Messenger(deberia, con los metodos actuales). Anadir metodoName() stringque retorne"matrix". -
2.2 Refactorizar
shell/matrix/Listenerpara que implementeplatform.EventSource:- El
EventHandlercallback ya recibedecision.MessageContext— solo necesita ajustar la firma deRun(ctx)si difiere - Internamente sigue usando mautrix syncer, pero externamente expone la interfaz generica
- El
-
2.3 Crear wrapper
shell/matrix/Platformque componga Client + Listener e implementeplatform.Platform
Fase 3: Desacoplar runtime.go
-
3.1 Cambiar el campo
matrix *matrix.ClientenAgentstruct pormessenger platform.Messenger -
3.2 Cambiar
listener *matrix.Listenerporsources []platform.EventSource -
3.3 Actualizar
Run()para arrancar multiples EventSources en goroutines:for _, src := range a.sources { go src.Run(ctx) } -
3.4 Actualizar
handleEventpara que no reciba*event.Event— actualmente solo usaevt.RoomIDque ya esta enMessageContext.RoomID. Eliminar la dependencia demautrix/event. -
3.5 Actualizar todas las llamadas directas a
a.matrix.SendXxx()ya.matrix.SendTyping()para usara.messenger.SendXxx(). Puntos clave:handleEvent— typing indicator, command replies, unknown commandexecuteActions— ya pasa por el runner, OKhandleTaskEvent— typing indicator, send replyrunLLM— tool call notices
-
3.6 Actualizar
shell/effects/runner.go:- Renombrar interfaz
MatrixSenderaMessenger(o importarplatform.Messenger) - El Runner ya recibe la interfaz, solo cambia el nombre
- Renombrar interfaz
-
3.7 Actualizar
New()constructor para recibir[]platform.Platformen vez de construir matrix.Client internamente. Mover la creacion de clientes de plataforma al launcher.
Fase 4: Implementar shell/telegram/
-
4.1 Elegir libreria de Telegram Bot API para Go. Opciones:
- (A)
github.com/go-telegram-bot-api/telegram-bot-api/v5— la mas popular, estable - (B)
github.com/gotd/td— cliente completo (MTProto), mas complejo - (C) HTTP directo contra Bot API — minimo, sin dependencias extra
- Recomendacion: opcion (A) por madurez y simplicidad
- (A)
-
4.2 Crear
shell/telegram/client.go:- Struct
Clientcon el bot API client interno - Constructor
New(cfg config.TelegramCfg) (*Client, error) - Implementar
platform.Messenger:SendText—tgbotapi.NewMessage(chatID, text)SendMarkdown—tgbotapi.NewMessageconParseMode: "MarkdownV2"SendReplyMarkdown—ReplyToMessageIDen el message configSendTyping—tgbotapi.NewChatAction(chatID, "typing")
- Struct
-
4.3 Crear
shell/telegram/listener.go:- Implementar
platform.EventSource - Modo long-polling con
GetUpdatesChan()(webhook es mas complejo y requiere dominio publico) - Convertir cada
tgbotapi.Updateadecision.MessageContext:SenderID= user ID de Telegram (string)SenderName= username o first_nameRoomID=telegram:<chat_id>(con prefijo de plataforma)Content= texto del mensajeIsDirectMsg= true si chat.Type == "private"IsMention= true si el mensaje contiene @botnameCommand= parsear si empieza con!(o/que es la convencion Telegram)
- Llamar al mismo
handleEvent(ctx, msgCtx)del Agent
- Implementar
-
4.4 Crear
shell/telegram/platform.goque componga Client + Listener e implementeplatform.Platform
Fase 5: Configuracion
-
5.1 Anadir tipos de config en
internal/config/schema.go:telegram: enabled: false bot_token_env: "TELEGRAM_TOKEN_BOT" allowed_chats: [] # lista de chat IDs permitidos (vacio = todos) command_prefix: "/" # convencion Telegram, ademas de "!"Struct:
TelegramCfgcon camposEnabled,BotTokenEnv,AllowedChats,CommandPrefix -
5.2 Anadir
TelegramCfgal config principal del agente (junto aMatrixCfg) -
5.3 Actualizar
internal/config/loader.gopara parsear la nueva seccion -
5.4 Actualizar
cmd/launcher/main.gopara instanciar plataformas segun config:var platforms []platform.Platform if cfg.Matrix.Enabled { platforms = append(platforms, matrixPlatform) } if cfg.Telegram.Enabled { platforms = append(platforms, telegramPlatform) } -
5.5 Anadir
TELEGRAM_TOKEN_<BOT>a.env.example
Fase 6: Tool matrix_send → platform_send
-
6.1 Evaluar si
matrix_sendtool debe ser generico o especifico:- Opcion A: renombrar a
send_messagecon parametroplatform— mas flexible - Opcion B: mantener
matrix_sendy anadirtelegram_send— mas simple - Recomendacion: opcion A a largo plazo, pero para esta task basta con que el LLM
responda por la misma plataforma que recibio el mensaje (ya lo hace via
handleEvent→ runner) matrix_sendcomo tool explícita solo se usa para enviar a rooms arbitrarios; si no se necesita eso en Telegram, no hace faltatelegram_sendahora
- Opcion A: renombrar a
-
6.2 Si se opta por generalizar: crear
tools/send.goconNewPlatformSend(messenger platform.Messenger)que el LLM pueda usar para enviar a cualquier plataforma
Fase 7: Tests
-
7.1 Unit tests para
pkg/platform/types.go— verificar que las interfaces compilan y los helpers de PlatformID funcionan -
7.2 Unit tests para
shell/telegram/client.go— mock del bot API, verificar conversion de mensajes -
7.3 Unit tests para
shell/telegram/listener.go— mock de updates, verificar conversion a MessageContext -
7.4 Integration test: verificar que un Agent con dos plataformas (matrix mock + telegram mock) recibe y responde correctamente por ambas
-
7.5 Verificar que todos los agentes existentes siguen funcionando solo con Matrix (backward compat)
Fase 8: Documentacion
-
8.1 Actualizar
CLAUDE.md— anadirshell/telegram/a la estructura de directorios, actualizar diagrama de flujo -
8.2 Actualizar
docs/creating-agents.mdcon la seccion de configuracion multi-plataforma -
8.3 Actualizar
.claude/rules/create_agent.mdpara mencionar la secciontelegram:en config -
8.4 Anadir a
README.mdla seccion de soporte Telegram
Orden de ejecucion recomendado
- Fase 1 (interfaces) — base para todo lo demas
- Fase 2 (adaptar matrix) — asegurar que Matrix sigue funcionando con las nuevas interfaces
- Fase 3 (desacoplar runtime) — el refactor central; debe compilar y pasar tests con solo Matrix
- Fase 5 (config) — preparar el config antes de implementar Telegram
- Fase 4 (implementar telegram) — el codigo nuevo
- Fase 6 (tools) — ajustar si es necesario
- Fase 7 (tests) — validar todo
- Fase 8 (docs) — ultima, cuando todo este estable
Decisiones de diseno pendientes
- Memoria compartida vs separada: Si un usuario habla por Matrix y por Telegram, son windows separadas (por el prefijo de plataforma en roomID). Podria unificarse en el futuro con un "user identity" cross-platform, pero no es necesario ahora.
- Comandos
/vs!: Telegram usa/como convencion para comandos de bot. Soportar ambos prefijos (/y!) en el parser de comandos, configurable por plataforma. - Webhooks vs long-polling: Empezar con long-polling por simplicidad. Webhook requiere HTTPS publico y es una optimizacion posterior.
- Presence: Matrix tiene presence (online/offline). Telegram no tiene equivalente nativo para bots. Abstraer como opcional en la interfaz.
- Reactions: Matrix tiene reactions (
m.reaction). Telegram tiene limitaciones. No incluir enMessengerpor ahora; dejarlo como extension especifica de plataforma.
Dependencias nuevas
github.com/go-telegram-bot-api/telegram-bot-api/v5 # Telegram Bot API client
Riesgos
- El refactor de runtime.go (Fase 3) es el mas delicado — cambia el corazon del sistema. Hacer commits atomicos despues de cada sub-tarea.
- Asegurar backward compatibility: un agente sin
telegram:en su config debe funcionar exactamente como antes.