Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
32bebbd
MAESTRO: add grammy dependency for Telegram provider
chr1syy May 10, 2026
052d9b5
MAESTRO: add Telegram provider config loader
chr1syy May 10, 2026
3da832d
MAESTRO: add Telegram topics DB wrapper
chr1syy May 10, 2026
50a375f
MAESTRO: register telegram_agent_topics migration
chr1syy May 10, 2026
77119e5
MAESTRO: document Telegram provider env vars in .env.example
chr1syy May 10, 2026
89ce101
MAESTRO: add Telegram provider adapter with lifecycle stubs
chr1syy May 10, 2026
a327760
MAESTRO: add Telegram voice helpers (isVoiceMessage, downloadVoice, a…
chr1syy May 10, 2026
96bef35
MAESTRO: add Telegram messageHandler with allowlist + voice transcrip…
chr1syy May 10, 2026
55650c2
MAESTRO: wire Telegram messageHandler into adapter
chr1syy May 10, 2026
c1aacc3
MAESTRO: implement Telegram outbound (send/react/typing) for TG-04
chr1syy May 10, 2026
ad2a8a3
MAESTRO: detect Telegram chat mode (forum vs dm) at startup
chr1syy May 10, 2026
c42ba7c
MAESTRO: register Telegram bound channel in core agent_channels at st…
chr1syy May 10, 2026
9b6f484
MAESTRO: implement Telegram resolveConversation for forum + DM modes
chr1syy May 10, 2026
78ce85e
MAESTRO: implement Telegram findOrCreateAgentChannel with agent-name …
chr1syy May 10, 2026
761a1aa
MAESTRO: add Telegram /session new (and /new) command path
chr1syy May 10, 2026
526d7c7
MAESTRO: port Discord slash commands to Telegram command modules
chr1syy May 10, 2026
13f08dc
MAESTRO: add Telegram command dispatcher with @botname stripping
chr1syy May 10, 2026
2d536cb
MAESTRO: wire Telegram command dispatcher into messageHandler
chr1syy May 10, 2026
1164be9
MAESTRO: add Telegram setMyCommands deploy script
chr1syy May 10, 2026
9c1093c
MAESTRO: route deploy-commands by ENABLED_PROVIDERS
chr1syy May 10, 2026
f1ae0a5
MAESTRO: accept telegram in install.sh normalize_module
chr1syy May 10, 2026
d3ca8fa
MAESTRO: dispatch write_config by MODULE (discord|telegram)
chr1syy May 10, 2026
762f804
MAESTRO: implement write_config_telegram() walkthrough in install.sh
chr1syy May 10, 2026
d37eddc
MAESTRO: implement pick_telegram_agent() with numbered agent menu
chr1syy May 10, 2026
01b8ccc
MAESTRO: make config_complete() module-aware (discord/telegram)
chr1syy May 10, 2026
4cdc360
MAESTRO: route deploy_commands through provider-aware npm script
chr1syy May 10, 2026
82d8159
MAESTRO: document telegram option in install.sh header
chr1syy May 10, 2026
98e2632
MAESTRO: add newcomer-friendly Telegram setup walkthrough
chr1syy May 10, 2026
669a1d3
MAESTRO: add Telegram section and provider details to README
chr1syy May 10, 2026
081e133
MAESTRO: document Telegram provider in architecture.md
chr1syy May 10, 2026
119c3de
MAESTRO: add Telegram troubleshooting section to README
chr1syy May 10, 2026
7141da4
MAESTRO: correct /new to /session new in Telegram docs
chr1syy May 10, 2026
520fee7
Merge origin/rc into feat/telegram
chr1syy May 15, 2026
9c38f09
fix(telegram): address PR #41 CodeRabbit review
chr1syy May 15, 2026
32d46f0
fix(installer): address second-round PR #41 CodeRabbit review
chr1syy May 15, 2026
20b108e
fix(telegram): address Codex review findings on PR #41
chr1syy May 16, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,11 @@ SLACK_BOT_PUBLIC_URL= # optional: public HTTPS URL for Bolt's Expres
SLACK_PORT=3000 # optional: HTTP port for Bolt's ExpressReceiver (webhook mode only, default 3000)
SLACK_ALLOWED_USER_IDS=U123,U456 # optional: comma-separated Slack user IDs allowed to use slash commands
SLACK_MENTION_USER_ID= # optional: Slack user ID to @mention when --mention is used

# --- Telegram provider (loaded only if 'telegram' is in ENABLED_PROVIDERS) ---
# Setup walkthrough: see docs/telegram-setup.md
TELEGRAM_BOT_TOKEN= # from @BotFather (https://t.me/BotFather → /newbot)
TELEGRAM_CHAT_ID= # the supergroup ID (negative number, e.g. -1001234567890) or DM chat ID
TELEGRAM_AGENT_ID= # the Maestro agent this bot is bound to (one bot = one agent)
TELEGRAM_ALLOWED_USER_IDS= # comma-separated Telegram user IDs allowed to interact with the bot
TELEGRAM_MENTION_USER_ID= # optional: Telegram user ID to @mention when --mention is used
6 changes: 4 additions & 2 deletions AGENTS-providers.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
# Provider development guide

This document is the deep-dive companion to [`AGENTS.md`](AGENTS.md) (and [`docs/architecture.md`](docs/architecture.md)) for adding a new chat-platform provider to Maestro Relay. Discord and Slack are already built-in (see [`docs/discord.md`](docs/discord.md) and [`docs/slack.md`](docs/slack.md)); everything below is what you'd need to know to ship a Teams, Matrix, etc. adapter without touching the kernel.
This document is the deep-dive companion to [`AGENTS.md`](AGENTS.md) (and [`docs/architecture.md`](docs/architecture.md)) for adding a new chat-platform provider to Maestro Relay. Discord, Slack, and Telegram are already built-in (see [`docs/discord.md`](docs/discord.md), [`docs/slack.md`](docs/slack.md), and [`docs/telegram-setup.md`](docs/telegram-setup.md)); everything below is what you'd need to know to ship a Teams, Matrix, etc. adapter without touching the kernel.

If you're adding behavior to an existing provider rather than building a new one, work in `src/providers/discord/` or `src/providers/slack/` and consult the matching `docs/<name>.md` instead.
If you're adding behavior to an existing provider rather than building a new one, work in `src/providers/discord/`, `src/providers/slack/`, or `src/providers/telegram/` and consult the matching `docs/<name>.md` instead.

> **Single-agent providers**: Some providers (like Telegram) bind one bot to exactly one agent. In that case, enforce the binding in `findOrCreateAgentChannel` by throwing when the requested `agentId` doesn't match the bound one — this keeps `/api/send` from leaking cross-agent traffic into the wrong chat. Users running the provider standalone set `ENABLED_PROVIDERS=<name>`; users running it alongside Discord/Slack set `ENABLED_PROVIDERS=discord,<name>` (or any subset).

## The kernel/provider boundary

Expand Down
25 changes: 19 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

[![Made with Maestro](https://raw.githubusercontent.com/RunMaestro/Maestro/main/docs/assets/made-with-maestro.svg)](https://github.com/RunMaestro/Maestro)

**Maestro Relay** connects chat platforms to [Maestro](https://runmaestro.ai) AI agents through `maestro-cli`. Discord and Slack ship in the box; Teams, Matrix, and others can be added by dropping in a provider adapter — the kernel is provider-agnostic.
**Maestro Relay** connects chat platforms to [Maestro](https://runmaestro.ai) AI agents through `maestro-cli`. Discord, Slack, and Telegram ship in the box; Teams, Matrix, and others can be added by dropping in a provider adapter — the kernel is provider-agnostic.

> **Migrating from `discord-maestro`?** Same codebase, new name. The legacy `maestro-discord` binary is preserved as an alias and all `DISCORD_*` env vars work unchanged. See "Migration" below.

## Features

- Provider-pluggable kernel — Discord and Slack today, Teams/Matrix next
- Provider-pluggable kernel — Discord, Slack, and Telegram today, Teams/Matrix next
- Creates dedicated channels for Maestro agents
- Per-user session threads (`/session new` or by mentioning the bot)
- Per-conversation FIFO queue with typing/reaction indicators
Expand All @@ -18,7 +18,7 @@
## Prerequisites

- Node.js 22+
- A bot token for at least one supported provider (Discord or Slack)
- A bot token for at least one supported provider (Discord, Slack, or Telegram)
- [Maestro CLI](https://docs.runmaestro.ai/cli) on your `PATH`

## Install (production one-liner)
Expand Down Expand Up @@ -49,7 +49,7 @@ The legacy aliases `maestro-bridge-ctl` and `maestro-discord-ctl` still work for
| systemd user / launchd agent | Auto-start unit |

Override any of these with `MAESTRO_RELAY_HOME`, `XDG_CONFIG_HOME`, or `MAESTRO_RELAY_BIN_DIR`. Pin a specific version with `MAESTRO_RELAY_VERSION=v1.0.0`.
Choose a provider module at install time via `MAESTRO_RELAY_MODULE` (`discord` or `slack`).
Choose a provider module at install time via `MAESTRO_RELAY_MODULE` (`discord`, `slack`, or `telegram`).

## Install (development from source)

Expand All @@ -70,11 +70,11 @@ cp .env.example .env
Set core values in `.env`:

```
ENABLED_PROVIDERS=discord # comma-separated; default 'discord'. Use 'slack' or 'discord,slack' for multi-provider deployments
ENABLED_PROVIDERS=discord # comma-separated; default 'discord'. Use 'slack', 'telegram', or any combination (e.g. 'discord,slack')
API_PORT=3457 # optional, default 3457
```

Then fill in the provider-specific keys. The Discord provider needs `DISCORD_BOT_TOKEN`, `DISCORD_CLIENT_ID`, and `DISCORD_GUILD_ID` — see [docs/discord.md](docs/discord.md) for bot setup, the full env-var reference, and slash-command deployment. The Slack provider needs `SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET`, `SLACK_TEAM_ID`, and `SLACK_APP_ID` — see [docs/slack.md](docs/slack.md). For optional voice transcription (Discord), see [docs/voice.md](docs/voice.md).
Then fill in the provider-specific keys. The Discord provider needs `DISCORD_BOT_TOKEN`, `DISCORD_CLIENT_ID`, and `DISCORD_GUILD_ID` — see [docs/discord.md](docs/discord.md) for bot setup, the full env-var reference, and slash-command deployment. The Slack provider needs `SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET`, `SLACK_TEAM_ID`, and `SLACK_APP_ID` — see [docs/slack.md](docs/slack.md). The Telegram provider needs `TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID`, and `TELEGRAM_AGENT_ID` — see [docs/telegram-setup.md](docs/telegram-setup.md) for the full BotFather walkthrough. For optional voice transcription, see [docs/voice.md](docs/voice.md).

3. Deploy slash commands (Discord):

Expand Down Expand Up @@ -119,10 +119,23 @@ npm run build && node --test --experimental-test-coverage dist/__tests__/**/*.te
| -------- | ---- | ------ |
| Discord | [docs/discord.md](docs/discord.md) — bot setup, env vars, slash commands, runtime behavior | Built-in |
| Slack | [docs/slack.md](docs/slack.md) — app setup, env vars, slash commands, runtime behavior | Built-in |
| Telegram | [docs/telegram-setup.md](docs/telegram-setup.md) — BotFather walkthrough, forum-topic-per-session, DM fallback, bot-per-agent binding | Built-in |
| Teams / Matrix / … | [AGENTS-providers.md](AGENTS-providers.md) — provider development guide | Add your own |

Optional voice transcription (whisper.cpp, Discord-only today): [docs/voice.md](docs/voice.md).

## Telegram

Bot-per-agent model: each Telegram bot represents one Maestro agent. Recommended setup is a forum supergroup where each session becomes its own topic; DM mode is supported for single-session use.

### Quick start

```bash
MAESTRO_RELAY_MODULE=telegram bash -c "$(curl -fsSL https://raw.githubusercontent.com/RunMaestro/Maestro-Relay/main/install.sh)"
```

The full newcomer walkthrough — creating a bot via @BotFather, picking a chat, collecting the IDs the installer asks for — lives in [docs/telegram-setup.md](docs/telegram-setup.md).

## How it works

Mention the bot or run `/session new` in an agent channel to create a thread, then chat — messages are queued and forwarded to the agent via `maestro-cli`. See [docs/architecture.md](docs/architecture.md) for the full message flow and kernel/provider split, and [AGENTS-providers.md](AGENTS-providers.md) for the provider-development guide.
Expand Down
49 changes: 28 additions & 21 deletions bin/maestro-relay-ctl.sh
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ Commands:
restart Restart the relay service
status Show service status
logs Tail service logs (Ctrl+C to stop)
deploy Deploy slash commands to Discord
deploy Deploy chat commands for enabled providers (Discord slash commands, Telegram bot commands)
update Reinstall the latest release (preserves config)
uninstall Remove the relay, service files, and CLI symlinks
version Print installed version
Expand Down Expand Up @@ -143,18 +143,33 @@ cmd_logs() {
}

config_complete() {
local file="$1" key value
local file="$1" key value enabled_providers provider
[ -f "$file" ] || return 1
local enabled_module
enabled_module="$(sed -nE 's/^[[:space:]]*ENABLED_PROVIDERS[[:space:]]*=[[:space:]]*([^#[:space:]]+).*/\1/p' "$file" | head -n1)"
enabled_module="${enabled_module#\"}"; enabled_module="${enabled_module%\"}"
enabled_module="${enabled_module#\'}"; enabled_module="${enabled_module%\'}"
local required_keys
if [ "$enabled_module" = "slack" ]; then
required_keys="SLACK_BOT_TOKEN SLACK_SIGNING_SECRET SLACK_TEAM_ID SLACK_APP_ID"
else
required_keys="DISCORD_BOT_TOKEN DISCORD_CLIENT_ID DISCORD_GUILD_ID"
fi
enabled_providers="$(sed -nE 's/^[[:space:]]*ENABLED_PROVIDERS[[:space:]]*=[[:space:]]*([^#[:space:]]+).*/\1/p' "$file" | head -n1)"
enabled_providers="${enabled_providers#\"}"; enabled_providers="${enabled_providers%\"}"
enabled_providers="${enabled_providers#\'}"; enabled_providers="${enabled_providers%\'}"
[ -n "$enabled_providers" ] || enabled_providers="discord"
# Validate every enabled provider's required env vars (split CSV), not just
# the first match — ENABLED_PROVIDERS=discord,telegram must pass only when
# both credential sets are present.
local IFS=','
local required_keys=""
for provider in $enabled_providers; do
provider="${provider// /}"
case "$provider" in
telegram)
required_keys="$required_keys TELEGRAM_BOT_TOKEN TELEGRAM_CHAT_ID TELEGRAM_AGENT_ID"
;;
slack)
required_keys="$required_keys SLACK_BOT_TOKEN SLACK_SIGNING_SECRET SLACK_TEAM_ID SLACK_APP_ID"
;;
discord|'')
required_keys="$required_keys DISCORD_BOT_TOKEN DISCORD_CLIENT_ID DISCORD_GUILD_ID"
;;
*) return 1 ;;
esac
done
unset IFS
for key in $required_keys; do
value="$(sed -nE "s/^${key}=([^#[:space:]]+).*/\1/p" "$file" | head -n1)"
[ -n "$value" ] || return 1
Expand All @@ -172,15 +187,7 @@ cmd_deploy() {
if ! config_complete "$env_file"; then
die "Config at $env_file is incomplete or contains template values. Edit it before running deploy."
fi
local enabled_providers
enabled_providers="$(sed -nE 's/^[[:space:]]*ENABLED_PROVIDERS[[:space:]]*=[[:space:]]*([^#[:space:]]+).*$/\1/p' "$env_file" | head -n1)"
enabled_providers="${enabled_providers#\"}"; enabled_providers="${enabled_providers%\"}"
enabled_providers="${enabled_providers#\'}"; enabled_providers="${enabled_providers%\'}"
[ -z "$enabled_providers" ] && enabled_providers="discord"
case ",$enabled_providers," in
*,discord,*) (cd "$INSTALL_DIR" && node dist/providers/discord/deploy.js) ;;
*) die "Discord is not enabled in ENABLED_PROVIDERS=$enabled_providers" ;;
esac
(cd "$INSTALL_DIR" && npm run deploy-commands --silent)
}

cmd_update() {
Expand Down
19 changes: 19 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,17 @@ The kernel speaks only in `IncomingMessage` / `OutgoingMessage` / `ChannelTarget
- Persists the maestro session id on the first response via `conv.persistSession`
5. Errors are logged to `logs/errors.log` and surfaced as a `⚠️` reply in the channel.

## Message flow (Telegram)

Telegram uses a **bot-per-agent** model: at install time the bot is bound to one Maestro agent (`TELEGRAM_AGENT_ID`) and one chat (`TELEGRAM_CHAT_ID`). One bot serves exactly one agent for its lifetime.

1. **Forum mode**: user sends `/session new` in the supergroup main feed → adapter calls `bot.api.createForumTopic`, registers the new topic in `telegram_agent_topics`, and treats that topic as one Maestro session. Subsequent messages in the topic are routed to that session.
2. **DM mode**: the bound chat is a single shared session. `/session new` clears the stored session id so the next message starts a fresh maestro session.
3. Each message becomes an `IncomingMessage` with `channelId = chatId` (DM) or `chatId:topicId` (forum) and is passed to `ctx.enqueue`.
4. The kernel queue serializes per `(provider, channelId)` exactly as for Discord — reactions/typing, `resolveConversation`, attachment download, `maestro.send`, response splitting, usage footer, session persistence.
5. Outbound: `provider.send` posts via `bot.api.sendMessage`, attaching `message_thread_id` when the target is a forum topic. Long responses are split at the 4096-char Telegram message limit.
6. `findOrCreateAgentChannel(agentId)` enforces the single-agent binding by throwing if `agentId !== TELEGRAM_AGENT_ID` — agent-initiated messages from `/api/send` for any other agent are rejected.

## Thread ownership (Discord)

Each thread is bound to the user who created it (via mention or `/session new`).
Expand All @@ -52,6 +63,7 @@ Each thread is bound to the user who created it (via mention or `/session new`).
| ------------------------- | --------------------- | --------------------------------------------------- |
| `agent_channels` | core | `(provider, channel_id)` → agent + session + flags |
| `discord_agent_threads` | discord provider | Thread → channel + agent + owner + session |
| `telegram_agent_topics` | telegram provider | `(chat_id, topic_id)` → agent + session |

The schema upgrades on first start: legacy `agent_channels` (single-PK `channel_id`) is rebuilt with composite PK `(provider, channel_id)` and existing rows defaulted to `discord`; legacy `agent_threads` is renamed to `discord_agent_threads`.

Expand All @@ -73,6 +85,13 @@ The schema upgrades on first start: legacy `agent_channels` (single-PK `channel_
| `src/providers/discord/voice.ts` | Discord voice-message detection |
| `src/providers/discord/commands/` | Slash command handlers |
| `src/providers/discord/deploy.ts` | Registers slash commands with Discord API |
| `src/providers/telegram/adapter.ts` | TelegramProvider implementing BridgeProvider |
| `src/providers/telegram/messageHandler.ts` | Telegram update → IncomingMessage |
| `src/providers/telegram/voice.ts` | Telegram voice-message detection + download |
| `src/providers/telegram/topicsDb.ts` | `telegram_agent_topics` accessor |
| `src/providers/telegram/commands/` | Slash command handlers (dispatched by messageHandler) |
| `src/providers/telegram/deploy.ts` | Registers commands via `bot.api.setMyCommands` |
| `src/providers/telegram/config.ts` | `TELEGRAM_*` env loading |
| `src/cli/maestro-relay.ts` | CLI tool for agent → chat messaging |
| `src/index.ts` | Kernel orchestrator (entry point) |

Expand Down
Loading