DeepSeek Harness plugin

dsh-telegram-control

Remote-control plugin for DeepSeek Harness (dsh): drive agents, jobs and status from a Telegram bot

Jump to install

Source facts

Repository
jackControls/dsh-telegram-control
Latest update
Aug 20, 2026
Category
Remote & Mobile
GitHub stars
0
Format
plugin
Catalog evidence
Upstream dsh.bundle evidence
Evidence path
package.json#dsh.bundle
Checked against
0.1.0-rc.8
Upstream check date
2026-08-20

This evidence comes from the upstream catalog. This site has not installed, run, or security-reviewed the plugin.

Install

Start with a prompt that asks an agent to review the GitHub repository and source. Switch to the command if you want to install it yourself.

Copy this prompt into DSH, Codex, or another agent and ask it to review the GitHub repository and source first.

Do not install or run any commands yet. Read this plugin's GitHub repository, README, and relevant source code. Then answer the questions below clearly and directly so I can decide whether it fits my needs:

1. What is this plugin, and what problem does it solve?
2. Who is it for, and what are its typical use cases?
3. How is it used after installation? Include one minimal example.
4. What known limitations or privacy, security, compatibility, or maintenance risks does it have?
5. Give a clear recommendation: recommend, conditionally recommend, or do not recommend, with reasons.

Distinguish statements documented by the repository, inferences from source code, and unknowns. If evidence is insufficient, say so explicitly. Do not guess or simply repeat the README.

GitHub: https://github.com/jackControls/dsh-telegram-control
Plugin: dsh-telegram-control
Author: jackControls

Check the source files

Read the README and other files from this plugin directory before installing.

File explorer3 files
README.mdSource · read only

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/request asks) 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/watch forwards 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

  • dsh running 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

1. Get a bot token: message @BotFather, run /newbot, and copy the token. 2. Install the plugin into a profile. Either from this repository

``sh dsh plugin --profile web add github:jackControls/dsh-telegram-control ``

or, from a local checkout:

``sh dsh plugin --profile web add /path/to/dsh-telegram-control ``

(Replace web with the profile you run. The package declares dsh.bundle, so dsh plugin add installs it and activates it as a profile layer automatically — you don't need to touch cordis.patch.yml. lib/ is committed, so git installs work without a build step.) 3. Configure via environment in the same process that runs dsh:

``sh export DSH_TELEGRAM_TOKEN='123456:ABC-DEF...' export DSH_TELEGRAM_ALLOWED_CHATS='123456789,987654321' # comma-separated chat ids ``

4. Restart dsh. Start a private chat with your bot, send /chatid to 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 keyEnv fallbackDefaultMeaning
tokenDSH_TELEGRAM_TOKEN— (required)Telegram bot token.
allowedChatIdsDSH_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.
apiBasehttps://api.telegram.orgBot API base (useful for proxies and tests).
defaultAgentIdnoneSession id plain messages target when the chat has no /agent selection.
pollTimeoutSec50Long-poll getUpdates timeout (Telegram accepts up to 50).
replyTimeoutMs600000 (10 min)Max wait for an agent reply before flushing partial output with a note.
showToolCallsfalseEmit one-line 🔧 <name> notices while a reply is pending.
maxMessageChars4000Per-message character cap before Telegram-side splitting.

Commands

CommandWhat it does
/help, /startCommand list.
/statusUptime, conversation count (live/total), background job count.
/agentsList 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.
/jobsList background jobs.
/kill <job id>Request a background job be stopped.
/cancelCancel the selected agent's current turn.
/watch / /unwatchToggle forwarding live agent output to this chat.
/chatidShow 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-polling getUpdates loop 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(...)) with source: { 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/event feed and the live agent/inbox/claimed /

agent/status / agent/error / agent/disposed events. Each follow-up's message id is matched against agent/inbox/claimed to learn its turn number, and the accumulated reply (visible text + 💭 reasoning) is flushed when that turn's turn/end lands — 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

against allowedChatIds (or DSH_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 stored userId is 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 — /unwatch is 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