2756557498
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>
212 lines
10 KiB
Markdown
212 lines
10 KiB
Markdown
# 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.go` depende directamente de `*matrix.Client` y `*matrix.Listener`
|
|
- `shell/effects/runner.go` ya define `MatrixSender` como interfaz, pero con nombre acoplado
|
|
- `decision.MessageContext` es **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.go` con las interfaces puras:
|
|
```go
|
|
// 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 `PlatformID` como prefijo para room IDs multi-plataforma:
|
|
- Formato: `matrix:!abc123:server.com`, `telegram:chat_456`
|
|
- Crear helpers `PrefixRoomID(platform, rawID) string` y `ParseRoomID(prefixed) (platform, rawID)`
|
|
- Esto permite que la memoria y windows no mezclen contextos entre plataformas
|
|
|
|
### Fase 2: Adaptar shell/matrix/ a las interfaces
|
|
|
|
- [ ] **2.1** Verificar que `shell/matrix/Client` ya satisface `platform.Messenger` (deberia, con los metodos actuales). Anadir metodo `Name() string` que retorne `"matrix"`.
|
|
|
|
- [ ] **2.2** Refactorizar `shell/matrix/Listener` para que implemente `platform.EventSource`:
|
|
- El `EventHandler` callback ya recibe `decision.MessageContext` — solo necesita ajustar la firma de `Run(ctx)` si difiere
|
|
- Internamente sigue usando mautrix syncer, pero externamente expone la interfaz generica
|
|
|
|
- [ ] **2.3** Crear wrapper `shell/matrix/Platform` que componga Client + Listener e implemente `platform.Platform`
|
|
|
|
### Fase 3: Desacoplar runtime.go
|
|
|
|
- [ ] **3.1** Cambiar el campo `matrix *matrix.Client` en `Agent` struct por `messenger platform.Messenger`
|
|
|
|
- [ ] **3.2** Cambiar `listener *matrix.Listener` por `sources []platform.EventSource`
|
|
|
|
- [ ] **3.3** Actualizar `Run()` para arrancar multiples EventSources en goroutines:
|
|
```go
|
|
for _, src := range a.sources {
|
|
go src.Run(ctx)
|
|
}
|
|
```
|
|
|
|
- [ ] **3.4** Actualizar `handleEvent` para que no reciba `*event.Event` — actualmente solo usa `evt.RoomID` que ya esta en `MessageContext.RoomID`. Eliminar la dependencia de `mautrix/event`.
|
|
|
|
- [ ] **3.5** Actualizar todas las llamadas directas a `a.matrix.SendXxx()` y `a.matrix.SendTyping()` para usar `a.messenger.SendXxx()`. Puntos clave:
|
|
- `handleEvent` — typing indicator, command replies, unknown command
|
|
- `executeActions` — ya pasa por el runner, OK
|
|
- `handleTaskEvent` — typing indicator, send reply
|
|
- `runLLM` — tool call notices
|
|
|
|
- [ ] **3.6** Actualizar `shell/effects/runner.go`:
|
|
- Renombrar interfaz `MatrixSender` a `Messenger` (o importar `platform.Messenger`)
|
|
- El Runner ya recibe la interfaz, solo cambia el nombre
|
|
|
|
- [ ] **3.7** Actualizar `New()` constructor para recibir `[]platform.Platform` en 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
|
|
|
|
- [ ] **4.2** Crear `shell/telegram/client.go`:
|
|
- Struct `Client` con el bot API client interno
|
|
- Constructor `New(cfg config.TelegramCfg) (*Client, error)`
|
|
- Implementar `platform.Messenger`:
|
|
- `SendText` — `tgbotapi.NewMessage(chatID, text)`
|
|
- `SendMarkdown` — `tgbotapi.NewMessage` con `ParseMode: "MarkdownV2"`
|
|
- `SendReplyMarkdown` — `ReplyToMessageID` en el message config
|
|
- `SendTyping` — `tgbotapi.NewChatAction(chatID, "typing")`
|
|
|
|
- [ ] **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.Update` a `decision.MessageContext`:
|
|
- `SenderID` = user ID de Telegram (string)
|
|
- `SenderName` = username o first_name
|
|
- `RoomID` = `telegram:<chat_id>` (con prefijo de plataforma)
|
|
- `Content` = texto del mensaje
|
|
- `IsDirectMsg` = true si chat.Type == "private"
|
|
- `IsMention` = true si el mensaje contiene @botname
|
|
- `Command` = parsear si empieza con `!` (o `/` que es la convencion Telegram)
|
|
- Llamar al mismo `handleEvent(ctx, msgCtx)` del Agent
|
|
|
|
- [ ] **4.4** Crear `shell/telegram/platform.go` que componga Client + Listener e implemente `platform.Platform`
|
|
|
|
### Fase 5: Configuracion
|
|
|
|
- [ ] **5.1** Anadir tipos de config en `internal/config/schema.go`:
|
|
```yaml
|
|
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: `TelegramCfg` con campos `Enabled`, `BotTokenEnv`, `AllowedChats`, `CommandPrefix`
|
|
|
|
- [ ] **5.2** Anadir `TelegramCfg` al config principal del agente (junto a `MatrixCfg`)
|
|
|
|
- [ ] **5.3** Actualizar `internal/config/loader.go` para parsear la nueva seccion
|
|
|
|
- [ ] **5.4** Actualizar `cmd/launcher/main.go` para instanciar plataformas segun config:
|
|
```go
|
|
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_send` tool debe ser generico o especifico:
|
|
- Opcion A: renombrar a `send_message` con parametro `platform` — mas flexible
|
|
- Opcion B: mantener `matrix_send` y anadir `telegram_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_send` como tool explícita solo se usa para enviar a rooms arbitrarios; si no se necesita eso en Telegram, no hace falta `telegram_send` ahora
|
|
|
|
- [ ] **6.2** Si se opta por generalizar: crear `tools/send.go` con `NewPlatformSend(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` — anadir `shell/telegram/` a la estructura de directorios, actualizar diagrama de flujo
|
|
|
|
- [ ] **8.2** Actualizar `docs/creating-agents.md` con la seccion de configuracion multi-plataforma
|
|
|
|
- [ ] **8.3** Actualizar `.claude/rules/create_agent.md` para mencionar la seccion `telegram:` en config
|
|
|
|
- [ ] **8.4** Anadir a `README.md` la seccion de soporte Telegram
|
|
|
|
---
|
|
|
|
## Orden de ejecucion recomendado
|
|
|
|
1. **Fase 1** (interfaces) — base para todo lo demas
|
|
2. **Fase 2** (adaptar matrix) — asegurar que Matrix sigue funcionando con las nuevas interfaces
|
|
3. **Fase 3** (desacoplar runtime) — el refactor central; debe compilar y pasar tests con solo Matrix
|
|
4. **Fase 5** (config) — preparar el config antes de implementar Telegram
|
|
5. **Fase 4** (implementar telegram) — el codigo nuevo
|
|
6. **Fase 6** (tools) — ajustar si es necesario
|
|
7. **Fase 7** (tests) — validar todo
|
|
8. **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 en `Messenger` por 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.
|