ghbhiee / dsh-plugin-discord

Listed

Discord bridge for DeepSeek Harness: chat with your dsh web sessions from Discord

mainSession View source

Installation

npx -y @deepseek-ai/dsh plugin --profile web add github:ghbhiee/dsh-plugin-discord

This installation command is an unverified starting point generated from the GitHub repository address.

README

Maintainer-authored documentation snapshot.

View on GitHub ↗
Commit ec1c37bSynced Aug 18, 2026

dsh-plugin-discord

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.

  • Messages from allowlisted Discord users/channels are relayed into a dsh session; the reply comes back as Discord messages (long replies are split, code fences survive the split).
  • /new starts a fresh session named like [Discord] 08-18 15:04 — the pinned title marks its origin in the web sidebar.
  • Sessions are created through the same agent-preset composition the web host uses, so they carry the full toolchain and can be opened, continued, and renamed in the web UI at any time. A session created on the web can be adopted from Discord with /use <id>.
  • Zero runtime dependencies: the gateway runs on Node's built-in WebSocket, REST on built-in fetch.

Commands

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.

CommandEffect
(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
/helpHelp

Any other /-prefixed text passes through as a prompt.

File transfer

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):

  • Agent → Discord: the agent writes a line [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 → agent: files/images the user attaches are saved under the session cwd's .discord-uploads/ and their paths are appended to the prompt, so the agent can read them directly.

Install

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.

Configuration

FieldDefaultMeaning
token""Bot token; empty falls back to tokenEnv, then tokenFile
tokenEnvDSH_DISCORD_TOKENEnvironment 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
tokenFileKeyDISCORD_BOT_TOKENKey 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
cwdhome dirWorking 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
maxChunksPerReply6Cap on Discord messages per reply (overflow truncated with a notice)
maxUploadBytes8000000Cap on one outgoing attachment ([discord-file: …])
uploadRoots[]Extra directories the agent may upload from (session cwd is always allowed)
maxIncomingBytes25000000Cap on one incoming Discord attachment saved to disk
stateFile""Channel→session binding file; empty derives one in the profile dir
typingIntervalMs8000Typing-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

Local end-to-end test without Discord

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.

Discord application setup

  1. Discord developer portal → your application → Bot.
  2. Enable the Message Content Intent (needed for guild-channel text; DMs work without it — the bridge automatically retries without the intent if refused and warns in the log).
  3. Invite the bot to your server, or just DM it.

How the shared-session part works

  • New sessions go through 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.
  • Resuming a cold session composes the preset recorded in its log, mirroring the web host's cold-resume path.
  • The bridge stamps each relayed prompt's message source with the Discord message id (the same pattern the web client uses with 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.
  • Model selection follows the session log (the web host's behavior), so switching models in the web UI carries over to later Discord turns.

Known limits

  • Replies arrive when the turn completes (typing indicator while it runs); no partial streaming edits yet.
  • Attachments/images from Discord are ignored — text only for now.
  • Approval prompts (tool permission questions) cannot be answered from Discord; sessions run with whatever approval policy the profile composes.
  • One bot token drives one gateway connection: do not reuse a token that another running bot (e.g. Hermes) is using, or you will knock it offline.

Project files and signals

Shown items are public repository signals detected in the directory snapshot.

TestsDetected

Repository information

Language
TypeScript
License
MIT
Last updated
Aug 18, 2026, 10:28 AM

Install deliberately

Review source code, permissions, lifecycle hooks, dependencies and network access. Test untrusted plugins in an isolated environment.