dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Telegram 远程控制插件:会话列表(标题+工作目录)、按需恢复暂停的持久化会话(重挂 preset)、按回合精准回传回复、💭 思考摘要、✅/❌ 内联按钮审批(结果回写原消息)、bot 命令菜单。
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.
/status, /agents, /agent <session id>, /jobs, /kill <job id>,
/cancel, /watch / /unwatch, /chatid, /help.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.setMyCommands, so /agents,
/agent, /jobs, … show up in the Telegram input field without being typed by hand./watch forwards every assistant message from live sessions to your chat.dsh running from the npm package (npx @deepseek-ai/dsh) or a repository checkout.fetch (Node ≥ 18; dsh itself needs Node ≥ 22).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-control
or, from a local checkout:
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.)
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 ids
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.
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]
| 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. |
| 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.
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.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.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.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.>=22 (declared in engines).>=0.1.0-rc.6 (declared in peerDependencies). Verified end to end against
both dsh 0.1.0-rc.6 (npm CLI) and 0.1.5-rc.2 (the version bundled by DSH Desktop) — the plugin
reads either persistence generation (inspect() or open(id,'read')), either session-history
accessor (events or snapshotEvents()), and either list() shape.@deepseek-ai/schemastery (config validation). Everything else
is a peerDependency resolved from the host installation; there are no postinstall scripts and
no bundled binaries.config.token or $DSH_TELEGRAM_TOKEN), and
writes exactly one state file ($DSH_HOME/telegram-control-state.json) recording which
conversation each chat selected. It does not read or write any other local file.npm test (24 unit tests) plus node tests/smoke.mjs, a two-phase
end-to-end run that installs the plugin into a disposable profile, boots a real dsh web, and
asserts the live-agent relay, paused-session resume, approval round-trip, and question
round-trip (32 checks). Set DSH_CLI to test a specific harness build.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)
/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).MIT