插件目录 / Network / dsh-telegram-control
dsh-telegram-control
已验证 · 实测可装 jackControls
功能简介
Telegram remote control for DeepSeek Harness (dsh): conversations with titles + workspace, on-demand resume of paused sessions, turn-tracked replies, inline-button approvals, reasoning summaries, bot command menu
可用 — 实测通过,早期项目
Telegram remote control for DeepSeek Harness (dsh): conversations with titles + workspace, on-demand resume of paused sessions, turn-tracked replies, inline-button approvals, reasoning summaries, bot command menu 实测能干净安装、正常启动。早期项目,但功能可用。
「已验证」表示我们的自动化 CI 在干净 profile 里实际执行了 dsh plugin add 并启动成功——仅此而已。功能描述与版本兼容性均为作者声明。这不是安全审计,也不代表对第三方代码的背书。
README
dsh-telegram-control
Remote-control plugin for DeepSeek Harness (dsh).
Runs a Telegram bot inside the harness process so you can drive your agents, jobs, and harness
status from your phone: send a message to the bot and it becomes a follow-up to your agent, whose
reply comes back to you as a Telegram message.
Everything is a plugin in dsh — this one is a Cordis function plugin that talks to the Telegram
Bot API over long-polling with zero runtime dependencies beyond the harness itself.
Features
- Remote agent control — plain text messages are sent as follow-ups to the chat's selected
conversation and appear as ordinary user messages in the desktop Web UI conversation. The agent's
reply — including its thinking/reasoning blocks, marked with 💭 — is relayed back the moment
its turn closes (turn-tracked, so delivery does not depend on the agent ever reporting idle). - Command surface —
/status,/agents,/agent <session id>,/jobs,/kill <job id>,/cancel,/watch//unwatch,/chatid,/help. - Approval on the phone — harness permission requests (sandbox escalations and other
approval/requestasks) arrive in Telegram with ✅ Allow once / ❌ Reject inline buttons;
the answer is applied and the message is edited with the outcome. If Telegram cannot be reached
the question falls back to the Web UI dialog instead of failing closed. - Command menu — the bot's slash commands are published via
setMyCommands, so/agents,/agent,/jobs, … show up in the Telegram input field without being typed by hand. - Live push —
/watchforwards every assistant message from live sessions to your chat. - Auth by chat allowlist — unknown chats get an onboarding hint with their chat id, nothing else.
- Safe output — all dynamic text is HTML-escaped before it reaches Telegram; long replies are
split into Telegram-sized chunks.
Requirements
dshrunning from the npm package (npx @deepseek-ai/dsh) or a repository checkout.- Node.js with a global
fetch(Node ≥ 18; dsh itself needs Node ≥ 22). - A Telegram bot token from @BotFather.
Installation
Get a bot token: message @BotFather, run
/newbot, and copy the token.Install the plugin into a profile. Either from this repository
dsh plugin --profile web add github:jackControls/dsh-telegram-controlor, from a local checkout:
dsh plugin --profile web add /path/to/dsh-telegram-control(Replace
webwith the profile you run. The package declaresdsh.bundle, sodsh plugin add
installs it and activates it as a profile layer automatically — you don't need to touchcordis.patch.yml.lib/is committed, so git installs work without a build step.)Configure via environment in the same process that runs
dsh:export DSH_TELEGRAM_TOKEN='123456:ABC-DEF...' export DSH_TELEGRAM_ALLOWED_CHATS='123456789,987654321' # comma-separated chat idsRestart
dsh. Start a private chat with your bot, send/chatidto learn your chat id if you
haven't added it yet, then/help.
Manual patch (no dsh plugin add)
If you prefer to mount it by hand, add a row to your profile's cordis.patch.yml
($DSH_HOME/profiles/<profile>/cordis.patch.yml):
- insert:
- id: telegram-control
name: 'dsh-telegram-control'
config:
# optional: pin values here instead of the environment
token: '123456:ABC-DEF...'
allowedChatIds: [123456789]
Configuration
| Config key | Env fallback | Default | Meaning |
|---|---|---|---|
token |
DSH_TELEGRAM_TOKEN |
— (required) | Telegram bot token. |
allowedChatIds |
DSH_TELEGRAM_ALLOWED_CHATS |
[] (deny all) |
Authorized chat ids. Note: schemastery validates a missing array to [], so an empty/absent allowlist always falls back to the environment. |
apiBase |
— | https://api.telegram.org |
Bot API base (useful for proxies and tests). |
defaultAgentId |
— | none | Session id plain messages target when the chat has no /agent selection. |
pollTimeoutSec |
— | 50 |
Long-poll getUpdates timeout (Telegram accepts up to 50). |
replyTimeoutMs |
— | 600000 (10 min) |
Max wait for an agent reply before flushing partial output with a note. |
showToolCalls |
— | false |
Emit one-line 🔧 <name> notices while a reply is pending. |
maxMessageChars |
— | 4000 |
Per-message character cap before Telegram-side splitting. |
Commands
| Command | What it does |
|---|---|
/help, /start |
Command list. |
/status |
Uptime, conversation count (live/total), background job count. |
/agents |
List every conversation — live agents and paused persisted sessions (the same set the Web UI sidebar shows): numbered, named by their session title, each with its workspace in brackets ([~/path]), status (idle/running/paused), model, and a 👈 marker on this chat's selection. |
/agent <number> |
Select the conversation at that position in the /agents list. |
/agent <name> |
Select by a case-insensitive substring of the conversation's title or session id; ambiguous matches list candidates. |
/agent <session id> |
Select by the exact session id. /agent with no argument shows the current selection. |
/jobs |
List background jobs. |
/kill <job id> |
Request a background job be stopped. |
/cancel |
Cancel the selected agent's current turn. |
/watch / /unwatch |
Toggle forwarding live agent output to this chat. |
/chatid |
Show this chat's id (for the allowlist). |
Plain messages are sent as follow-ups to the selected conversation. Selection order: the chat's/agent choice, then defaultAgentId, then the single conversation if there is exactly one. A
paused (persisted-but-not-live) conversation is resumed on first message exactly like the Web
UI does it — the session's stored agent preset is re-mounted, so history replays under the same
composition it was produced under. Each chat's selection is persisted to$DSH_HOME/telegram-control-state.json, so it survives harness restarts. Conversation names come
from the harness's session titles (session/title events — the auto-summary or your manual
rename, the same names the Web UI shows); the bracketed workspace is the session's cwd.
How it works
apply(ctx, config)runs a long-pollinggetUpdatesloop inside the harness process
(fetch-based, no bot framework). A 409 from the API (another poller) stops that poller cleanly;
network errors back off up to 30 s.- Plain messages call
agent.followup(createUserMessage(...))withsource: { kind: 'user' }—
the same source the Web UI's own input uses, so the text shows up as a normal user bubble in the
desktop conversation. - The plugin listens to the durable
session/eventfeed and the liveagent/inbox/claimed/agent/status/agent/error/agent/disposedevents. Each follow-up's message id is matched
againstagent/inbox/claimedto learn its turn number, and the accumulated reply (visible text +
💭 reasoning) is flushed when that turn'sturn/endlands — an idle flush and a timeout note
cover the remaining cases. Tool-call and error notices relay as they happen; typing indicators
show while the agent runs. - All registrations are Cordis effects, so unloading the plugin (HMR, profile reload) tears the
bot down cleanly.
Security
- Every inbound message is authorized before any action. Each update's chat id is checked
againstallowedChatIds(orDSH_TELEGRAM_ALLOWED_CHATS) before anything is dispatched: no
commands run, no messages reach an agent, and no approval answers are accepted for an
unlisted chat. A storeduserIdis never treated as authorization. - An empty allowlist denies everyone (fail closed): with no chat id configured, every
message is rejected and the only reply an unlisted chat ever receives is the onboarding hint
that tells it its own chat id. - The bot is a remote shell into your harness by design: only listed chats may issue
commands or answer approvals. Keep the allowlist tight. - The token is a bearer credential: prefer the environment variable over a committed patch file.
- The plugin does not widen any harness capability — it can only do what your running harness can
do, and the harness's own sandbox/approval policies still apply to agent work.
Development
npm install # dev deps (typecheck + build)
npx tsc # typecheck + emit lib/
node --test 'tests/*.test.mjs' # unit tests for the pure helpers
node tests/smoke.mjs # end-to-end: boots a real `dsh web` in an isolated $DSH_HOME,
# with fake Telegram + mock-LLM servers, and asserts the whole
# message→agent→relay loop (set DSH_CLI to your dsh bin if needed)
Known limitations
- Telegram webhooks are not supported; long-polling only (fine for a personal remote control).
- The plugin observes the session event feed; very high-frequency sessions could flood a watching
chat —/unwatchis your friend. sessionId-keyed reply buffering assumes one user drives one agent; two chats driving the same
agent get one combined reply per turn (each chat receives it).
License
MIT
安装
装一次目录插件,之后本站所有插件都能让 DeepSeek Harness 自动找、自动装:
dsh plugin add dshbase-catalog 然后对 agent 说「帮我装 dsh-telegram-control」,它会在目录里找到并自动安装。文档:dshbase-catalog · 已验证场景包。
该插件是 GitHub 源码(未发 npm)——直接从仓库装:
Web profile:
dsh plugin --profile web add github:jackControls/dsh-telegram-control Headless(CLI)profile:
dsh plugin --profile headless add github:jackControls/dsh-telegram-control 实测报告
验证通过:从 GitHub 源码完成 L1 安装 + L2 加载 + L3 运行(dsh 0.1.0-rc.6)。