dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
Settings page for the DeepSeek Harness desktop shell — README translation, GitHub proxy, market cache, and account top-up
English | 简体中文
Adds the sections missing from the official DeepSeek Harness (DSH) desktop settings page: README translation (DeepL) with a connectivity self-test, a GitHub acceleration proxy (self-hosted reverse proxy) with a connectivity self-test, plugin market cache cleanup, and an entry point for topping up the DeepSeek open platform account.
It installs as a standalone bundle into a DSH profile and does not modify any official code.
The official settings page already covers account / general / models / plugins / subagent / web-search / agent-loop / shell, but the following items have no entry point:
| Missing item | Impact |
|---|---|
| README translation | Most repositories in the plugin market only ship an English README, which makes their purpose hard to judge for Chinese-speaking users |
| GitHub acceleration proxy | Direct connections to GitHub are often slow or fail because of TLS handshake and time-to-first-byte latency; there is no shared place to configure an alternative route |
| Cache cleanup | No entry point to clear the market list cache, download leftovers, or the translation cache |
| Top-up entry | Users must find the top-up page themselves when the balance runs low |
This plugin adds only those four items. The following three sections are deliberately not
provided: "application name", "DSH data directory (DSH_HOME)", and "reuse configuration from
another DSH directory". They all presuppose a shell that carves out a fully isolated data
directory; the official desktop build does not isolate ~/.dsh and has a single home directory,
so those sections have no premise.
| Item | Requirement |
|---|---|
| Node.js | ^22.19.0 || >=24 |
| DeepSeek Harness | Official desktop build (the host half relies on its process.argv layout and profile structure) |
The host half only depends on ctx.webServer. The client half depends on the official client
module loader and on the slots provided by @deepseek-ai/dsh-client-ui-slots.
Install into the target profile with the pnpm bundled with the official desktop build:
# Install into a profile using the pnpm bundled with the official desktop build
pnpm add "file:<absolute path to the plugin directory>"
# Or install directly from GitHub
pnpm add "github:VCPr0j3k7/dsh-desktop-settings"
Example (replace the paths with your actual installation):
PNPM="D:/DeepSeek Harness/resources/runtime/pnpm/bin/pnpm.cjs"
NODE="D:/DeepSeek Harness/resources/runtime/primary-runtime/dependencies/node/bin/node.exe"
PROFILE="C:/Users/<user>/.dsh/profiles/desktop"
cd "$PROFILE"
"$NODE" "$PNPM" add "file:C:/path/to/dsh-desktop-settings"
After installing, you must also add the package name to the dsh.profile.bundles array in the
profile's package.json, otherwise the plugin will not load. This is the critical pitfall of
this project:
{
"dsh": {
"profile": {
"bundles": [
// …existing entries…
"dsh-desktop-settings"
]
}
}
}
pnpm add only records the package in dependencies and installs it into node_modules. A
plugin that declares dsh.bundle.patch must be registered in dsh.profile.bundles before it is
mounted. When it is installed but not registered, the plugin loads without any error and nothing
looks wrong in the UI — its settings section and sidebar entry simply never appear. The official
dsh plugin add command performs this step automatically; when running pnpm manually you have to
do it yourself.
The target harness must be restarted after installation. The host assembles the plugin tree only at startup, and a running instance does not hot-reload plugins.
To confirm it is active:
After restarting, the settings page should show a "桌面设置补充" section and the sidebar should show an "API 充值" entry.
Open the developer tools; the console should print:
[dsh-desktop-settings] client factory 已执行
[dsh-desktop-settings] 宿主基址 = (相对路径)
The first line means the client half was loaded; the second means it reached the host. If only the first line appears, the client failed to reach the host.
cd "$PROFILE"
"$NODE" "$PNPM" remove dsh-desktop-settings
Then remove the dsh-desktop-settings entry from dsh.profile.bundles and restart DeepSeek
Harness.
To disable it temporarily without uninstalling, just remove that entry from
dsh.profile.bundles and restart.
The plugin consists of two halves:
dsh-desktop-settings/
├── package.json declares dsh.bundle.patch and dsh.client.platform
├── cordis.patch.yml host-side mount declaration
├── index.js host half: Cordis plugin registering routes under /dsh-desktop-settings/api
├── client.js client half: window.__ModuleLoader__.load({ id, factory })
└── host/ adapter layer and service implementations
├── env.mjs resolves process.argv into paths and the host context
├── router.mjs HTTP facade (prefix dispatch, {ok,data|error}, event queue)
├── config.mjs shared configuration (<DSH_HOME>/dsh-extras.json)
├── system.mjs open browser / pick directory / open folder
├── logger.mjs output capture and log ring buffer
├── electron-shim.mjs stand-in for app.getPath('userData')
├── yaml-libs.mjs borrows the two YAML libraries the host itself uses (multiple candidate paths)
└── services/ translation, GitHub proxy, and settings helpers
kind: 'prefix' route on ctx.webServer with the prefix
/dsh-desktop-settings/api; dispatch inside the prefix is handled by host/router.mjs. There
is exactly one registration point, and the framework cleans it up on unload.{ ok: true, data } or { ok: false, error }, and business
failures also return HTTP 200. Status codes are reserved for transport-level facts such as
"route not found".GET /events?since=<seq>. SSE is not used because it cannot be confirmed whether the shell
buffers response bodies when forwarding a custom scheme; polling has no such unknown.dsh-app://app/), or an opaque origin
(location.origin === "null"). The transport layer therefore tries the candidate base URLs in
order on the first request (relative path → http://dsh.internal) and decides which one really
works from the response of /info.data.plugin equalling this plugin's id — not "returned 200", because the
SPA fallback route also answers unknown paths with 200 and a chunk of HTML.POST /settings/test-translation translates a fixed sentence and
returns the detected source language, the target language, and the translated text. A 456
response means the quota is exhausted; 403 means the key is invalid or the Free / Pro endpoint
is wrong.:fx and must use the api-free.deepl.com host. The authorization
header is Authorization: DeepL-Auth-Key <key>, not Bearer.Disabled by default with an empty address. A proxy address belongs to whoever set it up, so the plugin preloads no value and never silently routes a user's requests to a machine. When disabled, behaviour is identical to not having the feature.
It is active only when the toggle is on and the address is non-empty. With the toggle on but no address, it silently falls back to the official URLs instead of raising an error.
Addresses are normalised: a missing scheme, a trailing slash, and a reverse proxy mounted under a sub-path are all tolerated. Only http / https are accepted; anything else is treated as invalid and falls back to a direct connection.
Path prefixes map one-to-one onto upstream hosts; tokens, Accept headers, and request methods are passed through unchanged:
| Upstream host | Proxy path prefix |
|---|---|
api.github.com | /api |
raw.githubusercontent.com | /raw |
codeload.github.com | /codeload |
github.com | /gh |
objects.githubusercontent.com | /objects |
release-assets.githubusercontent.com | /release-assets |
302 responses are followed manually by this plugin (up to 6 hops): the Location value is
rewritten to the proxy address before the next request. If fetch followed them automatically,
it would connect straight to the redirect target, bypassing the proxy entirely.
Connectivity self-test: POST /settings/test-github-proxy fires two probes — the proxy root
path and one real raw request (the README of electron/electron). Both must pass: a healthy
root path only proves the reverse proxy is running, not that it can reach GitHub.
The optional githubToken is empty by default. When empty, requests are sent anonymously
(60 per hour); with a personal access token, the authenticated allowance applies (5000 per
hour). The token is only used for GitHub API access, is never written to logs, and is not
distributed with the plugin.
POST /market/clear removes the following:
| Target | Description |
|---|---|
<DSH_HOME>/plugin-data/market-cache.json | Plugin market list cache |
<DSH_HOME>/plugin-data/market-downloads | Download leftovers directory |
<DSH_HOME>/plugin-data/translate-cache/ | Translation cache |
If a single target cannot be removed it is skipped, without failing the whole operation.
GET /recharge/status returns the top-up URL, the usage URL, where the API key comes from, and
the current data directory.DEEPSEEK_API_KEY is checked first, then whether the key is present in
<DSH_HOME>/.credentials.yaml.POST /recharge/open opens https://platform.deepseek.com/top_up in the system default
browser. The URL is validated before opening: only http / https are allowed.All settings live in a single file, <DSH_HOME>/dsh-extras.json, shared with the plugin market
(dsh-plugin-market): this plugin writes it, and the market module also reads it because it needs
translation and downloads. A file is used instead of inter-plugin HTTP calls because a file has
no ordering problem of the form "the other plugin is not installed / has not started yet".
| Field | Default | Description |
|---|---|---|
appName | "DeepSeek Harness" | Display name |
githubProxy.enabled | false | Whether the GitHub acceleration proxy is enabled |
githubProxy.baseUrl | "" | Proxy address, e.g. http://1.2.3.4:8080 |
githubToken | "" | Optional GitHub API token |
translate.enabled | true | Whether README translation is enabled |
translate.endpoint | https://api-free.deepl.com/v2/translate | DeepL endpoint |
translate.apiKey | "" | DeepL API key |
translate.targetLang | "ZH" | Target language code (uppercase for DeepL) |
translate.characterCount | 0 | Locally accumulated number of translated characters |
translate.maxCharacters | 4000 | Maximum number of characters translated per README |
Configuration is never cached: the file is only a few hundred bytes, so re-reading it costs nothing and makes changes take effect immediately. Unknown fields are preserved as-is; missing fields fall back to their defaults.
On first run, if <DSH_HOME>/dsh-extras.json does not exist yet, the plugin attempts a one-time
migration of translate and githubProxy from %APPDATA%/DSH-Desktop/config.json (or
DSH Desktop, dsh-desktop), so previously entered keys and proxy addresses keep working.
The host half exposes the following routes under the /dsh-desktop-settings/api prefix.
| Method | Path | Description |
|---|---|---|
| GET | /info | Version, data directory, runtime directory, host state, and this plugin's id |
| GET | /config | Reads the full configuration (internal path fields excluded) |
| POST | /config | Deep-merges a patch into the configuration and returns the result |
| POST | /open-external | Opens a URL with the system default handler (http / https only) |
| POST | /pick-directory | Picks a directory: the official directoryPicker first, then a PowerShell folder dialog |
| GET | /restart-pending | Whether profile files were modified after the host became ready |
| POST | /restart-host | Deliberately refuses, see below |
| GET | /events | Incremental fetch from the event queue, parameter since |
| POST | /settings/test-translation | DeepL connectivity self-test |
| POST | /settings/test-github-proxy | GitHub acceleration proxy connectivity self-test |
| POST | /market/clear | Clears market cache, download leftovers, and translation cache |
| GET | /recharge/status | Top-up page information and API key source |
| POST | /recharge/open | Opens the top-up page |
| GET | /settings/inspect-homes | Deliberately reports "not applicable", see below |
| POST | /settings/import-from | Deliberately reports "not applicable", see below |
The request body limit is 8MB; unknown routes return 404 with a JSON body.
The three routes below exist but report a clear failure instead of being silently ignored or returning an empty result.
GET /settings/inspect-homes and POST /settings/import-from correspond to "reuse configuration
from another DSH data directory". That capability presupposes a shell with a fully isolated data
directory, whereas the official desktop build does not isolate ~/.dsh and has a single home
directory, so it does not apply. Both routes return a readable reason instead of 404, so that
anyone constructing a request by hand gets an explanation.
The full implementation of that capability is kept in host/services/settings-ops.mjs for
environments that do need isolation semantics.
POST /restart-host returns
插件无法重启宿主进程,请手动退出并重新打开 DeepSeek Harness.
In some shells DSH is a child process they spawned and can therefore be restarted. Here the plugin itself is part of what the host loaded, so it cannot make the host restart itself, and killing the process would also lose the user's sessions. The route therefore refuses honestly, and the UI shows "please restart manually".
| Half | Activation criterion |
|---|---|
| Host half | The package name appears in the profile's dsh.profile.bundles array; the host assembles the plugin tree at startup |
| Client half | dsh.client.platform === 'web', plus an environment that provides the official client module loader and the @deepseek-ai/dsh-client-ui-slots slots |
The client half registers into the following slots:
| Slot | Content |
|---|---|
settings.section | The "桌面设置补充" section (order: 30) |
sidebar.panellist | The "API 充值" sidebar entry (order: 44); clicking it opens the top-up page |
main | The top-up page panel |
The plugin library toggle in the official desktop build rewrites dsh.profile.bundles when saving
(it calls sanitizeProfile(...) internally to strip non-official bundles). If the plugin stops
working, check first whether that entry is still present.
npm test
# equivalent to
node test/check.mjs
test/check.mjs is an offline self-check that uses only Node built-ins and does not depend on the
DSH runtime. It covers:
name / inject / apply;dsh.bundle.patch in package.json really exists;dsh.client.platform === 'web' and the file referenced by exports['./client'] really exists;client.js is extracted and checked one by one
against the route table in index.js;host/config.mjs include the githubToken field.End-to-end verification requires a real machine: after installing and restarting the desktop build, the settings page should show the "桌面设置补充" section, the sidebar should show the "API 充值" entry, and both "测试连通性" buttons should return a result.
dsh.profile.bundles manually after installing. A manual pnpm install does
not do it, and the symptom of missing it is a completely silent plugin — no error, no log, and
no visible change in the UI.deploy/ghproxy.conf referenced in code comments is an nginx configuration example used when
setting up the proxy and is not distributed with this package. The path prefixes and locations
must match the table above; a mismatch raises no error and simply returns 404 quietly.githubToken is set, the
Authorization header travels over it in clear text; confirm the link is trustworthy first, and
prefer not to configure a token while the proxy is enabled.translate.characterCount only adds up
the characters this plugin sent and may not match the actual DeepL account usage; it is only
meant to show the rough scale on the settings page.process.argv layout, the
host half's path resolution breaks. That resolution is centralised in host/env.mjs; the
symptom is being unable to locate the profile or the bundled pnpm.