- Startseite
- Plugins
- Integrationen & Fernzugriff
- dsh-discord
dsh-discord
addozhang/dsh-discord
Discord-Adapter für dsh-Web- oder Desktop-Profile: @Mentions und Threads werden Sitzungen zugeordnet, Antworten gestreamt und Tool-Aktivitäten live angezeigt. Neun Slash-Befehle steuern Workspace-Bindung, Berechtigungsprofile und Modellauswahl. Tool-Freigaben und Nutzerfragen übernimmt Discord oder Web-UI, je nachdem, wer zuerst antwortet. Guild-Allowlist mit standardmäßiger Ablehnung.
Installation
dsh plugin --profile web add github:addozhang/dsh-discordREADME
@addozhang/dsh-discord
A Discord-first adapter for DeepSeek Harness: run DSH sessions from your Discord guild — mention the bot with a task, a thread opens, the answer streams in, and approvals and questions arrive as buttons you can answer from your phone.
No extra process: the adapter is a DSH plugin that mounts straight onto your dsh web profile. Session state stays in DSH; durable bindings live in the profile's storage domain.
Features
- Mention-driven sessions — an authorized
@bot <task>in a bound channel anchors a thread (your message becomes the first post), creates the DSH session, and submits the prompt at most once. Follow-ups inside the thread queue without a mention. Attached images ride along: they are downloaded from the Discord CDN within strict size/host bounds and submitted as image parts for multimodal models. - Stream rendering — typing indicator, one head message edited in place, per-tool activity rows, fenced long-answer splitting, one-time finalize; the activity message is deleted when the turn ends.
- Approvals & questions (dual surface) — DSH asks become buttons, select menus, and a free-text modal, mirrored to the web/desktop panel at the same time: the first surface to answer wins and the other auto-retires (Discord carries the ask alone when no web client is connected). Ownership is enforced (the asker — or the thread owner on later turns — clicks), expiry sweeps fail closed, and settled controls grey out in place.
- Session control —
/steer,/stop, and/queue list|removewith turn-ownership checks;/project bind|list|infoand/session resumefor guild↔workspace binding and history;/guild forgetfor operator cleanup. - Model selection —
/model showreads the session's live model directory;/model selectwalks an interactive provider → model → reasoning cascade, or applies a typedprovider/modeldirectly. Open to any authorized member by default; restrictable to Host operators. - Adapter status —
/infoshows the adapter version, connection state, and project links (feedback, docs). Works in any channel. - Permission switching —
/permission show/setread and switch the session's permission preset (bundled sandbox + approval tiers) through the Host's own/permissioncommand path, inheriting its journal audit pair;danger-full-accessrequires a button confirmation. - Settings card, bilingual out of the box — token onboarding and connect/disconnect (stored in the Host credential service, never in settings or logs), guild allowlist, auto-archive, and language. Every Discord-visible string ships in Chinese and English; the bot follows the DSH language preference or a pinned choice.
- Hardened by design — deny-first authorization inside an explicit guild allowlist. Mentions are suppressed twice:
allowed_mentionson every request, plus byte-level neutralization of the wire body. DSH submission is at-most-once with unknown-preserving reconciliation — an ambiguous delivery is never blindly resent. Bindings survive restarts, and the READY sweep rebuilds deleted category/control channels while treating a deleted workspace channel as user intent (the mapping retires; the workspace stays bindable).
Requirements
- The dsh CLI, running a web profile. Match the dsh and adapter channels — see Versions.
- Node.js
^22.19.0 || >=24 - A Discord application with a bot user and the MESSAGE CONTENT privileged intent enabled (Developer Portal → your application → Bot → Privileged Gateway Intents)
Versions
Two channels ship in parallel, each following the dsh host surface it was verified against. Pick the adapter and the dsh CLI from the same row:
| Channel | Adapter | dsh CLI | Install |
|---|---|---|---|
| stable (0.2.1 line) | 0.6.6 — npm latest | 0.2.1-rc.3+ — npm latest/next | dsh plugin --profile web add @addozhang/dsh-discord |
| next (0.2.1 line) | >=0.6.5-rc.1 — npm next | 0.2.1-rc.3+ — npm latest/next | dsh plugin --profile web add @addozhang/dsh-discord@next |
| Legacy (0.1.7 line) | 0.5.0 — exact version | 0.1.7-rc.1 — exact version | dsh plugin --profile web add @addozhang/dsh-discord@0.5.0 |
- Adapter
0.6.5+ships no dsh-internal packages. Thedsh-storage-domaindeclaration helpers (defineDomain/domainTable) are mirrored inside the plugin, and the host'sstorageDomainservice is consumed through a structural contract. Upgrading the dsh CLI or Desktop within the 0.2.1 line (including the exact-version plugin gate introduced in0.2.1-rc.5) therefore no longer disables this plugin. The host API surface is still guarded by the startup probe — a host lacking a required service surface refuses activation with one actionable diagnostic. - Existing installs of adapter
0.6.1–0.6.4: those versions carrieddsh-storage-domain@0.2.1-rc.3as a runtime dependency. Since dsh0.2.1-rc.5the host disables any profile-tree plugin row whose version does not exactly match the runtime — the carried copy shadows the host's own, thestorageDomainservice vanishes, and the plugin stays pending (no workspaces or sessions in the UI; data is untouched). Upgrade the adapter to0.6.5+to fix it. If you manually added a@deepseek-ai/dsh-storage-domainentry to the profile'spackage.jsonas a stopgap, remove it too — it will trip the same gate on the next host upgrade. - Earlier pairings are unchanged: adapter
0.6.0-rc.1↔ dsh0.2.0-rc.2(both sides are exact-version installs now that the dist-tags moved to 0.2.1), and adapter0.5.0pins the0.1.7-rc.1line symmetrically. Adapter>=0.5.0-rc.4(including0.5.0) speaks the 0.1.7 profile-backed settings forms and controller services and will not load on older hosts (the startup probe refuses with one actionable diagnostic);0.5.0-rc.1–rc.3target dsh0.1.6-alpha; adapter0.4.xtalks to the formerapiProxysurface, which0.1.6-alpharemoved — it remains the pairing for the dsh0.1.5stable line, reachable by exact version (e.g.@addozhang/dsh-discord@0.4.0). - To switch the dsh CLI line:
npm install -g @deepseek-ai/dsh@latest(0.2.1-rc.x, pairs with adapter 0.6.5+) ornpm install -g @deepseek-ai/dsh@0.1.7-rc.1(pairs with adapter 0.5.0). - Right after a
nextrelease, the@nextspec can resolve to a stale cached version. If the installed copy looks old, install by exact version instead (e.g.@addozhang/dsh-discord@0.6.5-rc.1) and verify theversionfield inside the profile'snode_modules.
Install
Install with the dsh CLI — it installs the package into the profile and registers the bundle for you. The stable channel is shown below; for the next channel use the @next command from the Versions table (up / rm take the same spec):
dsh plugin --profile web add @addozhang/dsh-discord
Then restart dsh web and refresh the browser. dsh plugin reconciles the profile's bundle list for you — nothing to edit by hand.
Upgrade and removal use the same command:
dsh plugin --profile web up @addozhang/dsh-discord # upgrade; the bundle list reconciles again
dsh plugin --profile web rm @addozhang/dsh-discord # remove; run /guild forget first to clean adapter records
If you manage a profile without the CLI, the manual equivalent is to add the package with pnpm inside the profile directory and list it in dsh.profile.bundles yourself:
{
"dsh": { "profile": { "bundles": ["@deepseek-ai/dsh-base", "@deepseek-ai/dsh-web-app", "@addozhang/dsh-discord"] } }
}
Configuration
All keys live in the dsh-discord settings namespace and can be set either from the settings card or by editing the profile's user settings (settings.yaml):
| Key | Default | Meaning |
|---|---|---|
enabled | false | Adapter master switch; the card's Connect starts it once a token is stored. |
allowedGuildIds | [] | Guild allowlist. Anything outside is ignored with zero adapter or DSH calls. |
memberUserIds / memberRoleIds | [] | Member-level authorization inside an allowed guild. |
administratorUserIds / administratorRoleIds | [] | Workspace-administrator level (/project bind). |
deniedUserIds / deniedRoleIds | [] | Deny entries; they win over every grant above. |
hostOperatorUserIds | [] | Host operators (/guild forget; /model select when modelSelectOperatorOnly is enabled). |
modelSelectOperatorOnly | false | Restrict /model select to Host operators (settings.yaml only; the card does not expose it). Default false: any authorized member may switch, and the switch still updates the Host default. |
permissionSelectOperatorOnly | true | Restrict /permission set to Host operators (settings.yaml only). Default true — stricter than /model because danger-full-access disables both sandbox and approval; loosen to let any authorized member switch. |
defaultVerbosity | essential-tools | Tool-activity row granularity: text-only, essential-tools, or full-tools. |
language | auto | Bot-visible copy language: auto follows the DSH language preference (non-Chinese renders English), or pin zh/en. |
streamUpdateIntervalMs | 800 | Coalescing budget for stream edits (250–10000). |
typingIntervalMs | 7000 | Typing-indicator heartbeat (1000–30000). |
approvalTimeoutMs | 600000 | Approval ask deadline (30000–86400000); overdue asks auto-reject. |
questionTimeoutMs | 1800000 | Question ask deadline (30000–86400000); expiry cancels the owning turn. |
threadAutoArchiveMinutes | 1440 | Task-thread auto-archive: 60, 1440, 4320, or 10080. |
dsh-discord:
allowedGuildIds: ["1517134847850709032"]
language: auto
The settings card exposes the three high-frequency fields (guild allowlist, auto-archive, language) plus the connection and token surface; every other key is fully supported through settings.yaml. An invalid stored section preserves the last known-good configuration.
Setup
- Invite the bot to your guild with at least: View Channels, Manage Channels (the adapter provisions its category and workspace home channels), Send Messages, Create Public Threads, Send Messages in Threads, Attach Files, Read Message History.
- Boot the profile and open the web UI.
- In Settings → Discord, paste the bot token (Developer Portal → your application → Bot → Reset Token) and press Connect. The token is stored by the Host credential service — never in settings, logs, or the client.
- Fill in Allowed servers (server IDs via Discord's Developer Mode → right-click a server → Copy Server ID). Everything outside this allowlist is ignored.
/model selectworks for any authorized member by default (single-user deployments). To restrict it to Host operators, add their IDs underhostOperatorUserIdsand setmodelSelectOperatorOnly: trueinsettings.yaml./guild forgetalways requires a Host operator.- Pick the bot language and mention the bot in a bound channel to start a session.
Commands
| Command | Where | What |
|---|---|---|
/project bind | any channel | bind the guild to a workspace (admin; provisions the home channel) |
/project list / info | any channel | list workspaces / inspect this channel's binding |
/queue list, /queue remove | session thread | inspect and trim the pending queue |
/steer, /stop | session thread | steer or cancel the running turn — any member of the thread may act on the current turn (the thread is a deliberately shared surface: single-user and small-team deployments; approvals/questions stay stricter — asker or thread owner only) |
/info | any channel | show the adapter version, connection state, and project links |
/model show / select | session thread | show the live model directory; select without arguments walks the interactive provider → model → reasoning cascade (any authorized member by default) |
/session resume | project channel | pick one of this workspace's past sessions (autocomplete: title and age, newest first) and resume it into a new thread of this channel; blank, already-bound, subagent, and archived sessions are never offered. The thread renders the session's history — answers and tool summaries plus the user's inputs as quoted echo lines (plugin/system injections are never echoed). After an adapter restart, threads catch up only what they missed: a persisted render watermark suppresses already-delivered history, and bound sessions re-subscribe on reconnect so turns prompted from the web UI still land in their thread |
/guild forget | any channel | operator-only removal of adapter records |
/permission show / set | session thread | read or switch the session's permission preset (view only / workspace edit / full access — bundled sandbox + approval tiers, catalog read live from the Host). set danger-full-access asks for a button confirmation first; presets autocomplete; Host operators only by default (permissionSelectOperatorOnly can loosen it). Every switch — from Discord or the web UI — renders one system line in the thread |
Design notes
- The adapter is a function/namespace plugin (
inject: ['sessionController', 'workspaceController', 'sessionQuery', 'webServer', 'credentials', 'settings', 'storageDomain', 'connection', 'commands', 'permissionPresets']) that mounts the Discord Gateway, command surface, stream renderer, and the settings card onto the DSH web profile. - The settings card is the first-run onboarding surface: the token entry writes the credential service's
DSH_DISCORD_BOT_TOKENref over the plugin management channel, then triggers the start chain. Disconnect keeps the credential; an empty reconnect uses it. - The publish workflow authenticates to npm via trusted publishing (OIDC) — no publish token is stored anywhere.
- The adapter start chain is generation-counted, so Connect/Disconnect races with the initial boot yield exactly one gateway.
- A credential probe falls back to
resolve()because the Host'sdescribe()misses env-sourced values — a connected adapter never reads as unconfigured. - Adapter logging is default-quiet: flow records ride the Host's debug level and failure-shaped events escalate to warn — nothing prints into the DSH process at the default level.
- Wire-level live-path tracing: set
DSH_DISCORD_TRACE=1before booting to emit mux frames, drop points, and delivery outcomes to stderr (default silent). It exists because the Host wires no plugin log exporter and exposes no log-level switch at this surface —logger.debugoutput is unobservable — and should be dropped once the Host grows one.
Known Limitations and Deferred Work
/preset,/skill, and/hoststay deregistered — their control modules are implemented and unit-tested and return when the router wires them (the/presetthread-context guard rides along).- Verbosity is a single global setting (the DSH ecosystem has per-channel precedent).
- Deferred after a Kimaki parity pass: reconcile-interactions wiring, typing pause during ask waits, fail-closed binding/session-owner store wiring, and credential-rotation watching.
- Known tension: the 250ms minimum stream-edit interval against Discord's edit budget under heavy load (429s self-heal), and typing has no duration-capped watchdog.
Development
pnpm install --ignore-scripts
pnpm test # 825 tests incl. gateway/REST twin E2E
pnpm typecheck
pnpm lint
pnpm build # lib + client bundle
To try a local build in a profile:
pnpm pack --pack-destination /tmp
dsh plugin --profile <your-profile> add file:/tmp/addozhang-dsh-discord-<version>.tgz
dsh plugin anchors relative path specs to the invoking directory, so from a checkout that has run pnpm build, dsh plugin --profile <your-profile> add ../fiber works too. Re-run pack + add to refresh the installed copy, then restart dsh web.
Releases are tagged (npm version <level> && git push --follow-tags) and published by GitHub Actions via npm trusted publishing (OIDC) — no publish token is stored anywhere. The implementation follows the OpenSpec change at openspec/changes/build-discord-native-adapter/ (design, capability specs, verification checklist, review reports).
License
MIT
Ähnliche Plugins
dsh-web (dsh-ssh)
zhu1090093659/dsh-web
dsh-web (dsh-remote-web-ui)
zhu1090093659/dsh-web
dsh-web-ui (dsh-ssh)
zhu1090093659/dsh-web-ui
dsh-web-ui (dsh-remote-web-ui)
zhu1090093659/dsh-web-ui