dsh-telegram-bot
Telegram client for DeepSeek Harness. It connects a DSH profile to a Telegram bot and keeps the same DSH sessions available in both interfaces.
Demo
First setup: https://youtu.be/QEeVucfBsWk
Installation, bot token, the allowlist, /start, /sessions and a test prompt.
Enable Threaded mode
BotFather: Bot Settings → Threaded mode → Enable. Then enable Disallow users to create new threads, so Telegram does not create a topic for every message sent in All.
Features
- One Telegram topic per DSH session; Web and Telegram use the same session.
- Telegram mirrors turns initiated from Telegram; turns started in the Web UI stay in Web. Automatic goal rounds for a Telegram-bound session are mirrored too, with thinking, tool calls and the final answer.
/sessionslists workspaces and existing sessions, and can create a new session in a selected workspace.- A
typing…action is shown in the topic while its turn runs and stops when the answer is ready. - Assistant answers stream into the topic with Telegram Markdown formatting. Messages sent while the agent is working are queued and processed after the current turn;
/clearremoves that queue without stopping the current answer. - Replies, quotes and forwards keep their context. Ordinary messages are passed unchanged; only replies, quoted parts and forwarded messages get a short context block. A replied-to photo or document is attached when Telegram allows the bot to download it.
- Reasoning and tool calls are shown as temporary messages and removed as soon as the final answer is ready. Each tool-call entry is capped at three mobile lines.
- Inline controls for model, reasoning effort, permission mode and context usage.
- Tool approvals with Deny / Allow buttons.
ask_user_questionsupport: options, multi-select, custom answers and plan review.- Documents and photos sent in a session topic are saved as DSH attachments and passed to the agent.
/new,/last,/goal,/rename,/fork,/clear,/stop,/restart,/statusand/menu./goalshows the current DSH goal and the command hints./goal <objective>creates or replaces the goal and starts working immediately;/goal edit <objective>updates the objective and resumes a paused, blocked or disarmed goal;/goal pause,/goal resumeand/goal clearmanage the current goal./goal <objective>and/goal edit <objective>replace the current panel with one fresh message; later goal changes edit that message in place. The panel is never pinned./newin a session topic starts a fresh DSH session in the same workspace and binds it to the same topic; the previous session stays in DSH. In All it opens a new session topic./fork [name]creates a DSH session fork in a new Telegram topic; without a name it uses the previous title plus the branch number.- The session picker also has 🔀 Continue in Telegram fork; automatic fork topics end with
(N). - ▶️ Continue in Telegram from All opens a fresh topic for the session. From inside a topic it continues in that topic. The previous topic stays in Telegram and can be deleted manually; reusing it from All is unsafe because Telegram can keep accepting messages for a topic deleted in the client.
- English and Russian bot messages.
- Bot token stays in the DSH credential store.
Requirements
- DSH
0.2.1-rc.2or newer, Node.js 22+. - A Telegram bot token from @BotFather.
- Threaded mode enabled in BotFather, Topics enabled in the bot chat, and Disallow users to create new threads enabled.
Install
From this repository:
npx @deepseek-ai/dsh plugin --profile web add .
From npm:
npx @deepseek-ai/dsh plugin --profile web add dsh-telegram-bot@latest
Restart dsh web after installing or updating the plugin. Then open Settings → Telegram.
Configure
| Field | Meaning |
|---|---|
| Bot token | Token from @BotFather; stored in the credential store |
tokenRef |
Credential/env name for the token; default TELEGRAM_BOT_TOKEN |
cwd |
Working directory for Telegram-created sessions |
agentPreset |
Preset mounted for Telegram sessions; default standard |
| Allowed users | Telegram user ids or @usernames |
| Allowed chats | Telegram chat ids or @channelnames |
| Show model thinking | Show reasoning in a temporary message |
| Show tool calls | Show tool calls in a temporary message |
| Bot language | en (default) or ru |
| Enable bot | Start or pause Telegram polling |
The token field only reports whether a token is configured. A token supplied through the environment cannot be overwritten from the page.
First run
- Create a bot with @BotFather and save its token in Settings → Telegram.
- In @BotFather open Bot Settings → Threaded mode, enable it, and enable Disallow users to create new threads.
- Open the bot chat, tap the bot name and enable Topics.
- Send
/startin All. With an empty allowlist the bot replies with your user and chat ids. - Add your user id to Allowed users and press Save.
- Send
/sessionsin All, choose a workspace and open or create a session. - Send a test message in the new topic.
An ordinary message in All creates a new session topic in the default workspace. Commands such as /sessions, /fork and /last still work in All.
If users are allowed to create threads, Telegram creates a topic before the bot receives the message; the bot then warns about the BotFather setting on startup.
Delete topics
/topic delete <name or thread id>deletes one known topic and its Telegram messages. Numeric targets must be real topic thread ids; ids larger than 2^31 (for example a user id) are rejected. Send/idinside the topic to read its id./topic wipeasks for confirmation first; send/topic wipe confirmto delete every topic known to the bot in this chat. DSH sessions themselves are not deleted.
Topics are known after the bot creates them, after /rename, or after Telegram reports a topic creation/rename. Unknown topics cannot be listed through the Bot API.
Send into a topic
Scripts, scheduled jobs, other plugins and agents can post into a topic by name or thread id. The message is also recorded in the session bound to that topic, without starting a turn; whoever answers there next sees it. If the topic has no session yet, the bot opens one in the configured workspace, as /new would.
CLI from the package:
dsh-telegram-send --topic "Release" "Build finished"
dsh-telegram-send --topic 54553 --file /tmp/report.pdf "Report attached"
dsh-telegram-send --topic "Release" --no-wait "Queued notification"
The request goes through a file spool under $DSH_HOME/telegram/outbox, so it needs no bot token and waits while DSH is stopped. Topic names ignore case; an unknown or ambiguous name is an error, and nothing is created.
Host plugins can call:
await ctx.get('telegramTopics').send('Release', 'Build finished')
The model-facing tool is telegram_send.
Troubleshooting topics
If every message in All creates a new topic, enable this BotFather option:
Bot Settings → Threaded mode → Disallow users to create new threads
The bot checks has_topics_enabled and allows_users_to_create_topics on startup, writes them to ~/.dsh/telegram/status.json, and posts a warning in the control chat when either setting is wrong.
A correct configuration contains:
"threadedMode": true,
"usersCanCreateTopics": false
Topic creation is single-shot: a network timeout is never retried, because retrying a createForumTopic that Telegram already committed creates a duplicate topic. Concurrent open requests for the same session are coalesced.
Private-chat topics send with message_thread_id. If Telegram answers message thread not found, the bot retries once with direct_messages_topic_id (the Bot API 10 direct-message-topic field). A send that Telegram accepts without returning any topic field is treated as undelivered, so a deleted topic is recreated instead of silently receiving messages in All.
Plugins page
The plugin page is informational. It points to Settings → Telegram and lists the manual configuration files. Use the bundle switch there to enable or disable the plugin; Enable bot is a soft pause that keeps the plugin loaded but stops polling.
Headless profile
The bot can run without the Web UI:
mkdir -p ~/.dsh/profiles/tg
cd ~/.dsh/profiles/tg
package.json:
{
"name": "dsh-profile-tg",
"private": true,
"dependencies": { "dsh-telegram-bot": "latest" },
"dsh": {
"profile": {
"bundles": ["@deepseek-ai/dsh-base", "dsh-telegram-bot"]
}
}
}
cordis.patch.yml:
- id: telegram-bot
name: dsh-telegram-bot
config:
enabled: true
allowedUsers: ["<user-id>"]
allowedChats: ["<chat-id>"]
Install and run:
npm install
printf 'TELEGRAM_BOT_TOKEN=%s\n' '<token>' >> ~/.dsh/.env
chmod 600 ~/.dsh/.env
npx @deepseek-ai/dsh --profile tg
Do not put the token in cordis.patch.yml.
Security
- The bot token lives in the credential store, not in the profile patch.
- Empty Allowed users answers only in private chats with the setup hint.
- Group and channel chats require an entry in Allowed chats.
- Do not run two pollers with the same token; Telegram returns
409 Conflict. - A DSH plugin runs in-process with the Host. Install only source you trust.
Development
No build step is needed.
npm install
node --check index.js
node --check client.js
npm test
src/app.js is the Host runtime: Telegram polling, topic routing and the session bridge. src/update-ledger.js persists Telegram update cursors, src/admission.js uses the native DSH session queue. src/formatting.js, src/state.js, src/status.js, src/utils.js, src/errors.js, src/config.js and src/constants.js hold the supporting layers. src/message-context.js builds reply/quote/forward context, src/topics.js resolves topic names, direct-message topic targets and delivery checks, and src/outbox.js powers dsh-telegram-send. src/index.js is the public entry point; root index.js is the loader-compatible wrapper. Root client.js is the loader-compatible client bundle. src/client.js contains the client source. The plugin registers the model-facing telegram_send tool for posting into a Telegram topic.
License
MIT
Credits
Markdown to Telegram formatting uses telegramify-markdown (MIT).
No comments yet. Be the first to write one.