dsh-experts (DSH Expert Marketplace)
An out-of-tree DSH bundle that brings the Awesome DSH Experts catalog (community experts and expert groups) into the DeepSeek Harness Web UI — without any changes to deepseek-harness itself.
DSH ("everything is a plugin") ships with no first-class "Expert / Expert Group" concept. This bundle fills that gap: a Settings → section tab (the same
settings.sectionslot the reference dshmarket client uses) to browse and copy experts, plus an optional compose-box entry to inject an installed expert group into the chat.
Overview
- Settings → section tab — browse the catalog by category/kind, search, view details, and copy a ready-to-paste
@expert <id>invocation block. Registered into the verifiedsettings.sectionslot. - Composer entry (chat input) — an optional button that opens a picker and copies an expert/group invocation block into the chat input. Note: deepseek-harness
0.1.0-rc.6does not expose a composer input slot, so this entry is expected to silently degrade in that version; the Settings-tab clipboard flow is the working path. The slot is configurable (DSH_EXPERT_MARKETPLACE_COMPOSER_SLOT). - Same-origin HTTP API —
POST /api/expert-marketplacewith methodsbootstrap,list,detail,groups,refresh,packs,packDetail. - Agent tools — registers
expert_list/expert_detailtools (best-effort, only when@deepseek-ai/dsh-toolsis available).
Compatibility
| Requirement | Value |
|---|---|
| DSH version | deepseek-harness@0.1.0-rc.6 (verified) |
| Profile | web (dsh.client.platform: "web") |
| Verified commit | see the repo's latest release tag / main HEAD |
| Node | >=18 (host side) |
| React | >=18 (peer, provided by the harness Web UI) |
The client bundle registers via the native __ModuleLoader__.load contract and only consumes the slots, locale, theme client services, so it degrades gracefully (never crashes the Web UI) on versions where those differ.
Install / Uninstall
Install (from git):
dsh plugin --profile web add https://github.com/fuchao2pku/dsh-experts.git
Install (local dev, symlinked):
dsh plugin --profile web add /path/to/dsh-experts
Then (re)start the Web profile:
dsh --profile web web
Uninstall:
dsh plugin --profile web remove dsh-experts
dsh --profile web web # restart to apply
Quick start
- Install the plugin and restart the Web profile (above).
- Open Settings → Expert Marketplace.
- Search or filter by category/kind; open an expert or expert group.
- Click Add to chat to copy its
@expert <id>invocation block to your clipboard, then paste it into the chat input.
Example: paste @software-team followed by a feature request to invoke the multi-role software team (product-manager → architect → engineer → QA).
Configuration
All options default sensibly and can be overridden via cordis.patch.yml or environment variables:
| Option | Env override | Default |
|---|---|---|
catalogUrl |
DSH_EXPERT_MARKETPLACE_CATALOG_URL |
the published catalog.json (companion repo awesome-dsh-experts) |
maxAgeMs |
— | 172800000 (48h) |
timeoutMs |
— | 15000 |
maxBytes |
— | 5000000 |
composerSlot |
DSH_EXPERT_MARKETPLACE_COMPOSER_SLOT |
composer.toolbar (set to "" to disable) |
composerIntegration |
— | true |
agentTools |
— | true |
Permissions & data
- Network: the plugin's host (Node) process fetches a single pre-built
catalog.jsonfromcatalogUrl(default: the companionawesome-dsh-expertsrepo's rawcatalog.json). The browser never contacts GitHub — it only calls the same-originPOST /api/expert-marketplace. - Storage: catalog data is cached in-memory, best-effort in the system temp dir (
<tmpdir>/dsh-experts/catalog.json), and a bundledcatalog-seed.jsonis the offline fallback. No personal files are read or written. - No secrets: the plugin ships no credentials and never transmits user data off-host except the catalog fetch above (same as any web page loading a static JSON).
- Untrusted catalog: the remote catalog is treated as untrusted input — every entry is schema-validated and never executed as code; only its human-readable text + a copyable
@expert <id>hint are surfaced in the UI.
Safety design (why it won't crash your DSH install)
This bundle was written to the explicit contract: installing it must never crash the host or the Web UI. Mechanisms:
- Zero build step — pure ESM JS, no
tsdown/TypeScript compile, so there is no build failure surface and every file is directly loadable + testable in plain Node. - Defensive perimeters — every external input (catalog JSON, HTTP params, agent-tool args, UI entries) is validated and never throws; failures become safe empty structures.
- Lazy optional dependencies — all
@deepseek-ai/*packages andreactare optional peer dependencies, imported lazily insidetry/catch. Missing them degrades a feature, never the plugin. - Per-feature
try/catchinapply()— web server route, agent tools, system-prompt section, and UI tabs are each independently guarded; one failing feature never prevents the others (or the plugin mount). - Bundled seed catalog — if the remote fetch fails, the last-good/seed catalog is served, so the Settings tab always renders.
- Double-guarded composer slot — Cordis throws when registering into an undeclared slot; both the
slots.injectandslots.registercalls are wrapped, so a wrong slot name for a given DSH version silently degrades the composer entry while the Settings tab keeps working. - Same-origin API guard — cross-origin requests are rejected with a structured
403instead of throwing.
Data source, updates & storage (experts / expert groups)
Short answer to "does dsh web load straight from the GitHub repo?" — No. The browser never touches GitHub. The host (Node) process fetches a pre-built catalog.json; the browser only calls the same-origin API /api/expert-marketplace. This mirrors how dshmarket sources its catalog.
- Source of truth:
experts/*.md(per-expert Markdown + YAML frontmatter) in the companion catalog repo awesome-dsh-experts. - Build:
scripts/scan.mjs(in that repo) aggregates them intocatalog.json(machine-readable, consumed by this plugin) andCATALOG.md(human-readable). - Update: that repo's
.github/workflows/scan.ymlregenerates the catalog on every push and on a daily cron (UTC 02:00). It runsscan.mjs --local --remote(local experts are always included, plus discovery of community repos taggeddsh-expert/dsh-expert-pack) and commits the refreshedcatalog.jsonback. - Runtime load:
src/host.jsCatalogSourceseeds from the bundledcatalog-seed.json, then refreshes fromcfg.catalogUrlon startup / Settings-tab open / manual refresh. Fetches are conditional (ETag / Last-Modified + 304) so re-fetching is cheap, exactly likedshmarket's registry fetch. - Storage / caching (3 layers): in-memory
entries; best-effort on-disk cache in the system temp dir; bundledcatalog-seed.jsonoffline fallback.
All fetch/cache failures are swallowed; the seed (or last good) wins and the UI shows a "catalog may be stale" hint.
Troubleshooting
| Symptom | Cause / Fix |
|---|---|
| Settings tab is empty | Catalog fetch failed and seed is empty. Check network to raw.githubusercontent.com; the tab shows a "stale" hint. Restart the Web profile to re-pull. |
| "Failed to load plugins" on startup | A client bundle didn't register via __ModuleLoader__.load. Ensure the plugin is the published version; this bundle always registers on load. |
| Composer button missing | Expected on 0.1.0-rc.6 (no composer slot). Use the Settings-tab Add to chat clipboard flow instead. |
| API returns 403 | Cross-origin call blocked. Only call /api/expert-marketplace from the same origin (the Web UI does this automatically). |
Development
# run the test suite (node --test, no external deps)
npm test
npm run test:verbose
# link into a local DSH web profile for manual testing
dsh plugin --profile web add /path/to/dsh-experts
dsh --profile web web
The bundle is plain ESM (no build). src/client.js is the browser half (ModuleLoader contract); src/host.js is the Node half (HTTP API + catalog source); src/catalog.js is the shared, dependency-free catalog model.
License & security
- License: MIT.
- Security model: the catalog is untrusted input — entries are schema-validated and never executed as code; only display text + a copyable
@expert <id>hint reach the UI. No network egress beyond the singlecatalog.jsonfetch, no filesystem writes outside the temp-dir cache, no secrets.
Tests
npm test # runs all suites (node --test, no external deps)
npm run test:verbose
27 tests cover: catalog model (parsing/validation/query/grouping), host route handler (every error path returns structured JSON, never throws), lifecycle (apply() survives a down network / missing services), client half (apply() never throws, degrades gracefully when slots/locale/composer are absent, never imports react in Node), and the client buildInvocationBlock.
No comments yet. Be the first to write one.