dsh-telegram
A Telegram channel for DeepSeek Harness. Message your bot, and the agent answers in the chat — the same agent, the same tools, the same session log as the Web UI.
Built for the case OpenClaw and Hermes already serve and Harness does not: reaching your agent from a phone, without leaving a shell open on a server that anyone with the bot's username can talk to.
What makes this one different
A Telegram bot wired to a coding agent is remote shell access through a chat app. Most bridges treat that as a detail. This one treats it as the design:
- Deny by default. An empty allowlist admits nobody. Access is by numeric Telegram user id, checked before a message reaches the agent. There is no "allow everyone" flag.
- In-chat approval for destructive tools.
bash,pwsh,terminal,write,editandstr_replace_editorstop at atools/pre-executegate and ask in the chat, with Allow / Deny buttons. Only the chat the prompt was shown in can answer it, and the callback token is a random UUID. An unanswered prompt denies after a timeout. - One agent per chat, disposed with the plugin. No shared session between users.
- No dependencies for the wire. The Bot API is called with
fetchand long polling — no webhook, no framework, no bot library to audit. The whole client is one small file.
Install
dsh plugin --profile web add github:sazzadurrahmaan/dsh-telegram
Then set the token and your user id:
export DSH_TELEGRAM_TOKEN='123456:ABC...' # from @BotFather
export DSH_TELEGRAM_ALLOWED_USER_IDS='123456789' # yours; see /whoami below
Or configure it in ~/.dsh/cordis.patch.yml:
plugins:
dsh-telegram:
token: ${DSH_TELEGRAM_TOKEN}
allowedUserIds: [123456789]
cwd: /home/dsh/workspace
Don't know your numeric id? Message the bot /whoami — that one command answers anyone, because it only tells you your own id, which you need before you can be allowlisted. Every other message from an unlisted user is dropped.
Commands
| Command | Effect |
|---|---|
/start |
Show the command list |
/new |
Dispose this chat's agent and start a fresh session |
/stop |
Cancel the running turn |
/status |
Session id and agent state |
/whoami |
Your Telegram user id — the only command answered without allowlisting |
Anything else is sent to the agent as a follow-up turn.
Configuration
| Key | Default | Meaning |
|---|---|---|
token |
$DSH_TELEGRAM_TOKEN |
Bot token from @BotFather |
allowedUserIds |
$DSH_TELEGRAM_ALLOWED_USER_IDS, else [] |
Numeric ids allowed to talk to the agent |
cwd |
harness default | Working directory for Telegram sessions |
showToolActivity |
true |
Post a line to the chat when a tool runs |
allowDestructiveTools |
false |
Skip the approval prompt entirely |
gatedTools |
shell and edit tools | Tools that require approval |
approvalTimeoutSec |
120 |
An unanswered approval denies after this |
pollTimeoutSec |
30 |
Long-poll timeout for getUpdates |
How it works
The plugin is an ordinary Cordis plugin on three extension points:
ctx.agents.create()mints one agent per chat;AgentHandle.dispose()tears it down.session/eventstreamsassistant/messageback to the chat, split at Telegram's 4096-character limit after a newline where one fits. The split is lossless — rejoining the parts returns the original — so indentation inside a code block survives.tools/pre-executereturns{ kind: 'deny' }for a gated tool the user did not approve.
Every registration goes through ctx.effect(), so a reload unwinds the poll loop and disposes live agents.
Security notes
Read these before you point this at a machine you care about.
- The allowlist is the only thing between a stranger and your shell. Anyone can find a bot by username and message it. Keep
allowedUserIdsaccurate and never set it from untrusted input. allowDestructiveTools: trueremoves the approval prompt. With it on, an allowed user can run any command the harness can run, from a phone, with no confirmation.- Group chats are not supported. Add the bot to a group and every member's messages arrive; only allowlisted ids are answered, but the surface is larger than intended. Use a direct chat.
- The token grants control of the bot. Keep it in the environment, not in a committed config file.
- Developer preview. Harness is
0.1.xand its README warns of compatibility-breaking changes. Pin your Harness version, or expect to re-fix this against seam changes.
Project
- CHANGELOG.md — what changed and when
- CONTRIBUTING.md — setup, and the invariants a change must keep
- SECURITY.md — threat model and how to report a vulnerability privately
- examples/cordis.patch.yml — a commented configuration to copy
Development
npm install --legacy-peer-deps # the published rc packages have an internal peer conflict
npm run build
npm test
--legacy-peer-deps is needed because @deepseek-ai/dsh-agent@0.1.0-rc.6 requires dsh-invariants ^0.1.0-rc.6 while the dsh-llm@0.0.1-rc.1 chain requires ^0.0.1-rc.1. Those packages are peer dependencies here — at runtime they are resolved from the host harness, so this affects local type-checking only.
License
MIT
No comments yet. Be the first to write one.