Telegram
This cell puts Nodecode in Telegram as a bot. People you allow can message it in a private chat or a group, and it answers there with the same agent you run in the terminal: same tools, same models, same store.
Each ask runs on its own, so two asks in one chat run at the same time. Reply to one of the bot's messages to keep talking about that answer. While a longer turn runs you see a card of its steps with Stop and Details buttons, and it stays above the answer as the turn's record.
The shared machinery (rooms, lanes, admission, the turn's card, commands, voice) is the channel kit's, in ../nodecode-channel-kit/. This folder holds what is Telegram's own: the getUpdates long poll, the Bot API calls, the config section and the setup probe.
Channel messages run with your machine's full authority. The bot refuses to start until you name who may talk to it.
Quick start
- Message @BotFather, send
/newbot, and pick a name. Save the token it answers with to a file only you can read, for example~/.nodecode/secrets/telegram.tokenwith mode 600, or put it in an environment variable. - Turn the cell on:
nodecode add channel-telegram, or/setupin the terminal. It ships with Nodecode, so nothing is downloaded. - Send your new bot a message. Telegram doesn't let a bot list its chats, so a chat only shows up for setup once it has written to the bot.
Add a
channels.telegramsection to~/.nodecode/config.jsonc:{ "channels": { "telegram": { "token_file": "/home/you/.nodecode/secrets/telegram.token", "allowed_users": ["<your user id>"], "owner": ["<your user id>"] } } }Or tell Nodecode "set up telegram; the token is in /home/you/.nodecode/secrets/telegram.token". It checks the token and lists the chats and people that have written to the bot, so you pick ids by name.
/setupin the terminal offers the same panel.- Restart the channels (the web page's Restart channels, or ask Nodecode to run
(restart-cells))./channelsin the terminal should readtelegram: running, connected. - Message the bot
hello.
In a group
Add the bot to the group and put the group's chat id (it is negative) in allowed_chats, or keep allowed_users as the gate. With require_mention on (the default) the bot answers when you @mention it or reply to one of its messages.
Telegram hides most group messages from bots. Mentions and replies to the bot still reach it, which is all require_mention needs. To let it read every message, send @BotFather /setprivacy, choose the bot, pick Disable, then remove the bot from the group and add it again.
In a forum group each topic is its own room. allowed_threads limits the bot to some topics; empty admits all of them.
What you see
- The card. A turn that takes a while posts one message that grows as it works: a pill (blue working, green done, red failed), the task, what it is doing now, its steps ticked off as they land, and its numbers in the footer. Stop ends the turn; Details shows whoever pressed it what the turn did.
- Words as they're written. With
streamon (the default) the answer appears in a message that is edited as the model writes. When it is done the answer posts fresh as a reply to your ask, so it notifies you, and the draft is deleted. - Markdown. Answers are Telegram rich messages: bold, lists, code, quotes and tables read as they do anywhere else. A very long answer comes in parts of up to 16,384 characters.
- Reactions. With
reactionson, your ask carries 👀 while its turn is active, then 👍 when the answer lands or 👎 if the turn fails. - Commands. The bot's
/menu is Nodecode's command list:/newstarts the chat fresh,/stopends a running turn,/modelsshows or changes the chat's model, and so on. Whenowneris set, every command but/helpis the owners' alone.
Files and voice
- Files in. The bot reads photos (the largest size sent), documents, videos, audio files, voice notes and round video notes. A caption counts as the ask's text. A file in the message you reply to comes along too.
- Voice in. A voice note reaches the agent as its transcript. Locally by default, so the audio never leaves your machine; the
transcriptionsection can point at an OpenAI-compatible server instead. - Files out. The agent can send files back: a picture shows inline, anything else comes as a document.
- Voice out.
voice_repliesadds a voice message under an answer:onanswers a voice note with one,ttsanswers every ask with one,off(the default) never does. It speaks with thespeechsection's voice and needs ffmpeg.
The agent can also call any Bot API method as the bot from its eval tool (nct:request): pin a message, react, read a chat or a member, change the command menu. The token never appears in what it reads back.
Who may talk to it
allowed_usersandallowed_chatsare the gate. At least one of them must name someone or somewhere, or the bot won't start.ownernames whose word is standing policy in the chat. An owner may always talk to the bot.- With
pairingon (the default), someone no list names who writes to the bot in a private chat gets a pairing code, and you are told./channels pair CODElets them in;/channels pairedlists who is in and/channels unpair USER-IDtakes someone out.
Configuration
Every key of channels.telegram. A present section is enabled; "enabled": false turns it off.
| Key | Default | What it does |
|---|---|---|
token_env | Environment variable holding the bot token. Use this or token_file. | |
token_file | File holding the bot token. The token never goes in the config itself. | |
allowed_chats | Chat ids the bot answers in. A group's id is negative; a private chat's is the user's id. | |
allowed_users | User ids allowed to drive the bot. | |
owner | User ids whose word is standing policy. | |
pairing | true | Answer unknown private chats with a pairing code. |
require_mention | true | In groups, answer only when @mentioned or replied to. |
allowed_threads | all | Forum topic ids the bot answers in. |
allow_private_chats | true | false ignores private chats. |
allow_group_chats | true | false ignores groups. |
allow_forum_topics | true | false ignores forum topics. |
bot_username | from getMe | The bot's @handle, for the mention check. |
reactions | false | Mark each ask with 👀, then 👍 or 👎. |
stream | true | Show the answer as it is written. |
voice_replies | "off" | "on", "tts" or "off"; see Files and voice. |
turn_budget_minutes | 0 | Minutes a turn may run before it must wrap up with what it has. 0 sets no limit. |
room_tokens | 40000 | The most history a chat keeps, in estimated tokens. Past it the older half is dropped from the chat's record (recall can still find it). 0 keeps everything. |
soul_file | A SOUL.md whose text is the chat's standing persona. | |
agents | Named agents a chat's asks may run as: a folder to work in, a prompt, a model, skills. | |
routes | Which asks run as which agent, by chat, topic or user id. | |
allowed_updates | ["message"] | Update kinds the poll asks Telegram for. Widen it for a layer that watches edits, reactions or membership changes. |
api_base | https://api.telegram.org | The Bot API server, for a local one. |
request_timeout_seconds | 60 | How long one Bot API call may take. |
get_updates_timeout_seconds | 2 | The long poll's window. Short on purpose: it is also how long a stop waits. |
poll_interval_ms | 1000 | Pause between polls. |
config.example.jsonc is a commented section to copy.
Troubleshooting
unauthorized (401): the token is wrong or was revoked. Ask @BotFather for a new one (/token) and save it where the config points.- A 409 conflict in the log: another program is polling with the same token, for example a second Nodecode or an old bot script. Telegram lets only one poll at a time. Stop the other one.
- Setup lists no chats: nobody has written to the bot yet, or the running lane is holding the update stream. Message the bot, or read the chat ids off
/channels. - The bot ignores a group: check that the group's id is in
allowed_chats(or that you are inallowed_users), and mention the bot or reply to it. If the log says it could not read the bot's username, setbot_username. - No reactions: some groups forbid them. Turn
reactionsoff there.
Testing
- From a Nodecode checkout,
just channels-testruns the kit's and the adapters' tests in one image with no network. - From this repository,
.github/test-cell.sh rooms/nodecode-channel-telegramruns this cell's tests alone against a pinned Nodecode tree, offline.
The tests drive the adapter through a scripted Bot API, so they need no Telegram account.