dsh-im
xmanrui
通过扫码或机器人凭据把IM机器人接入DeepSeek Harness(支持飞书、微信、钉钉、企业微信、QQ、Slack、Telegram、Discord和WhatsApp)。 Connect IM bots to DeepSeek Harness via QR code or credentials (9 channels).
PROJECT TOPICS
INSTALL REFERENCE
dsh plugin --profile web add github:ghbhiee/dsh-plugin-discord
该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。
PROJECT README
Discord bridge for DeepSeek Harness (dsh): chat with your dsh sessions from Discord — the very same sessions the web UI shows, so a conversation started at your desk continues on your phone through Discord, and vice versa.
/new starts a fresh session named like [Discord] 08-18 15:04 — the pinned title marks its origin in the web sidebar./use <id>.On startup the bridge registers these as real Discord slash commands (they
appear in the / picker). Plain-text forms keep working as a fallback — typing
/new and hitting Enter works even before registration propagates.
| Command | Effect |
|---|---|
| (any text) | Prompt the channel's current session (auto-creates one) |
/new [标题] |
New session, date-stamped [Discord] title |
/sessions (/list) |
Recent Discord-bridged sessions |
/use <id> (/switch, /resume) |
Bind this channel to a session — web-created ids work too |
/current (/session) |
Show the bound session and its status |
/stop (/cancel) |
Cancel the running turn |
/help |
Help |
Any other /-prefixed text passes through as a prompt.
Every deployment's bridge also serves its own bot as a push channel, so agents and daemons can message the user proactively instead of only replying. Because each dsh serves its own bot, sender identity follows the deployment: your local dsh notifies as its bot, your server's dsh as its bot.
What is automatic vs manual:
| Piece | Automatic? |
|---|---|
MCP server endpoint (POST /plugins/discord/mcp) |
✅ served on plugin boot, nothing to do |
HTTP API (POST /plugins/discord/api/notify) |
✅ served on plugin boot |
| Bearer secret | ✅ auto-generated on first boot → <profile dir>/discord-notify.secret (0600); override via config notifySecret or env DSH_DISCORD_NOTIFY_SECRET |
| MCP client registration (making an agent see the tool) | ❌ one-time manual step per client, recipes below |
Disable the whole surface with notifyEnabled: false.
POST http://127.0.0.1:<dsh port>/plugins/discord/api/notify
| Auth | Authorization: Bearer <secret> — read the secret from <profile dir>/discord-notify.secret (e.g. ~/.dsh/profiles/web/discord-notify.secret) |
| Body | {"content": "text", "userId"?: "...", "channelId"?: "..."} — JSON, content required |
| Target | default = DM of the first allowedUsers entry; userId = another user's DM; channelId = a guild channel the bot can post in |
| Content | Discord markdown; text over 2000 chars is split into several messages automatically |
| 200 | {"ok": true, "channelId": "...", "messageIds": ["..."]} |
| 400 | bad JSON / empty content |
| 401 | missing or wrong bearer token |
| 502 | Discord-side delivery failure ({"ok": false, "error": "..."}) |
curl -s -X POST http://127.0.0.1:3080/plugins/discord/api/notify \
-H "authorization: Bearer $(cat ~/.dsh/profiles/web/discord-notify.secret)" \
-H 'content-type: application/json' \
-d '{"content": "⚠️ 磁盘使用率 92%"}'
The endpoint is loopback by default (dsh binds 127.0.0.1): callers on the same
machine hit it directly; remote callers tunnel (ssh -L 3080:127.0.0.1:3080 host)
or go through whatever reverse proxy already fronts your dsh.
The MCP server side (Streamable HTTP, tool discord_notify) is already
running — these recipes just point a client at it.
dsh itself (gives every dsh agent the tool) — machine-wide
~/.dsh/cordis.patch.yml, then restart dsh:
- insert:
- id: mcp-discord-notify
name: '@deepseek-ai/dsh-mcp-client'
config:
serverName: discord
transport: streamable-http
url: http://127.0.0.1:3080/plugins/discord/mcp
headers:
authorization: Bearer <contents of discord-notify.secret>
Claude Code:
claude mcp add --transport http discord \
http://127.0.0.1:3080/plugins/discord/mcp \
--header "Authorization: Bearer $(cat ~/.dsh/profiles/web/discord-notify.secret)"
Stdio-only MCP clients (via mcp-remote):
{ "command": "npx", "args": ["-y", "mcp-remote", "http://127.0.0.1:3080/plugins/discord/mcp", "--header", "Authorization: Bearer <secret>"] }
Give an agent this to make it register the MCP itself (fill in your paths):
帮我接入本机 dsh 的 Discord 通知能力:MCP 端点是
http://127.0.0.1:3080/plugins/discord/mcp(transport: streamable-http), 鉴权头Authorization: Bearer <密钥>,密钥内容读文件~/.dsh/profiles/web/discord-notify.secret。请把它注册进你的 MCP 配置, 然后调用discord_notify工具发一条测试消息「MCP 接入成功」验证。
Give an agent this to make it use the HTTP API in code/scripts (no MCP needed):
本机有一个 Discord 推送接口,写监控/定时脚本需要通知我时调用它:
POST http://127.0.0.1:3080/plugins/discord/api/notify, 请求头Authorization: Bearer $(cat ~/.dsh/profiles/web/discord-notify.secret)和Content-Type: application/json,请求体{"content": "通知文本"}。 返回{"ok": true}即送达;非 200 时把状态码和响应体记入日志,不要重试超过 3 次。
Or drop this one-liner into an agent's standing instructions (CLAUDE.md 等), so it always knows the channel exists:
需要主动通知用户时(任务完成、告警、提醒),POST
http://127.0.0.1:3080/plugins/discord/api/notify,Bearer 密钥在~/.dsh/profiles/web/discord-notify.secret,body{"content": "..."}。
When the agent asks the user a question (dsh's ask_user_question tool — option
picks, confirmations, plan reviews), the bridge renders it as Discord inline
components in the bound channel: a select menu for the options (multi-select
supported), an ✏️ button that opens a modal for a free-text answer, and a
cancel button. Picking an option updates the message in place and the agent's
turn continues with the answer.
How it works: the harness dispatches questions on the Cordis waterfall
user-questions/request, and the bridge takes a prepended seat on it — the
browser's answerer claims a question outright, so a seat behind it would never
see one. From the front, the bridge posts the Discord card and hands the same
question on with next(), so both surfaces stay live and the first answer wins.
A web answer visibly closes the Discord card.
Requires dsh >= 0.1.1 (the waterfall). On older hosts the seat simply never fires and questions stay web-only.
Both directions work, and the agent is told about them (a capability notice is injected once per session when it is first driven from Discord):
[discord-file: /absolute/path]
in its reply; the bridge strips the line, and uploads the file as a Discord
attachment (images render inline). Only files under the session's working
directory (plus configured uploadRoots) and within maxUploadBytes are
sent — a refusal is reported in the reply..discord-uploads/ and their paths are appended to the
prompt, so the agent can read them directly.dsh plugin --profile web add github:ghbhiee/dsh-plugin-discord
Provide the bot token via the DSH_DISCORD_TOKEN environment variable of the
dsh process (recommended — nothing secret touches the profile YAML). Under a
launchd deployment that means the service plist's EnvironmentVariables dict;
under systemd, an Environment= line; in a shell, plain export.
Then configure the row in the profile's cordis.patch.yml:
- id: discord-bridge
config:
allowedUsers: ["<your discord user id>"]
# allowedChannels: ["<guild channel id>"] # empty = DM-only
cwd: /Users/you/projects # where new sessions live
# preset: "" # agent preset; empty = deployment default
# titlePrefix: "[Discord] "
Token lookup order: config.token → $DSH_DISCORD_TOKEN (name configurable
via tokenEnv) → tokenFile (raw token or an env-style file).
The bridge refuses to start when both allowlists are empty — an unrestricted bridge would hand your agent (and its tools) to anyone who can DM the bot.
| Field | Default | Meaning |
|---|---|---|
token |
"" |
Bot token; empty falls back to tokenEnv, then tokenFile |
tokenEnv |
DSH_DISCORD_TOKEN |
Environment variable consulted when token is empty |
tokenFile |
"" |
File holding the token — raw, or env-style KEY=value lines. Keeps the secret out of profile YAML |
tokenFileKey |
DISCORD_BOT_TOKEN |
Key looked up when tokenFile is env-style |
allowedUsers |
[] |
Discord user ids allowed to talk (required for DMs) |
allowedChannels |
[] |
Guild channel ids the bridge listens in; empty = DM-only |
cwd |
home dir | Working directory of newly created sessions (created if missing). An already-bound session keeps the cwd it was created with — use /new after changing this |
preset |
"" |
Agent preset for new sessions; empty composes the default |
titlePrefix |
"[Discord] " |
Title prefix marking Discord-originated sessions |
maxChunksPerReply |
6 |
Cap on Discord messages per reply (overflow truncated with a notice) |
maxUploadBytes |
8000000 |
Cap on one outgoing attachment ([discord-file: …]) |
uploadRoots |
[] |
Extra directories the agent may upload from (session cwd is always allowed) |
maxIncomingBytes |
25000000 |
Cap on one incoming Discord attachment saved to disk |
stateFile |
"" |
Channel→session binding file; empty derives one in the profile dir |
typingIntervalMs |
8000 |
Typing-indicator refresh while a turn runs |
gatewayUrl |
"" |
Gateway URL override — a test seam for scripts/fake-discord.mjs |
restBaseUrl |
"" |
REST origin override — same test seam |
scripts/fake-discord.mjs runs a scripted fake Discord (gateway + REST) on
ws://127.0.0.1:8931 / http://127.0.0.1:8932. Point the plugin at it via
gatewayUrl/restBaseUrl (any token), restart dsh, and the script drives a
whole conversation — session creation, /new, /use, context continuity —
against your real dsh deployment, printing every reply the bridge sends.
agentPresets.resolve/mount — the identical composition path session.create uses on the web wire — and are persisted by the host's session persistence, so session.list shows them like any other session.rpcId), then folds exactly the turn that prompt opened — a concurrently running web prompt in the same session is never mistaken for the Discord answer.CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: discord。