VCPr0j3k7/dsh-desktop-settings

dsh-desktop-settings

Settings page for the DeepSeek Harness desktop shell — README translation, GitHub proxy, market cache, and account top-up

Uncategorizedcordisdeepseek-harnessdsh

dsh-desktop-settings

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.


Contents

Background

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 itemImpact
README translationMost repositories in the plugin market only ship an English README, which makes their purpose hard to judge for Chinese-speaking users
GitHub acceleration proxyDirect 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 cleanupNo entry point to clear the market list cache, download leftovers, or the translation cache
Top-up entryUsers 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.

Requirements

ItemRequirement
Node.js^22.19.0 || >=24
DeepSeek HarnessOfficial 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.

Installation

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:

  1. After restarting, the settings page should show a "桌面设置补充" section and the sidebar should show an "API 充值" entry.

  2. 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.

Uninstallation

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.

How It Works

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

Host half

  1. Registers a single 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.
  2. Every response is uniformly { 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".
  3. Progress events go into a ring buffer (400 entries max), and the client polls increments via 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.

Client half

  1. The page is served by the official shell, so its origin can be one of three things: the host's own HTTP address, the shell's custom scheme (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.
  2. The criterion is 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.
  3. Once chosen, the base URL is remembered and reused for subsequent requests. If none of the candidates works, it falls back to the relative path, prints a single console line, and does not retry repeatedly.

Features and Settings

README translation (DeepL)

  • Enabled by default. Translation is only invoked for text that is essentially non-Chinese: a request is sent only when the text contains fewer than 30 CJK characters and their share is below 8%, so a genuinely Chinese README never consumes quota.
  • Keyword extraction runs entirely locally, is unaffected by the translation toggle, and works offline.
  • Translations are cached on disk per repository, so reopening a README never bills twice.
  • Connectivity self-test: 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.
  • DeepL Free keys end with :fx and must use the api-free.deepl.com host. The authorization header is Authorization: DeepL-Auth-Key <key>, not Bearer.

GitHub acceleration proxy

  • 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 hostProxy 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.

Cache cleanup

POST /market/clear removes the following:

TargetDescription
<DSH_HOME>/plugin-data/market-cache.jsonPlugin market list cache
<DSH_HOME>/plugin-data/market-downloadsDownload 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.

API top-up

  • GET /recharge/status returns the top-up URL, the usage URL, where the API key comes from, and the current data directory.
  • The API key source is only probed for existence; the key itself is never read or returned. The environment variable 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.
  • Clicking the "API 充值" sidebar icon opens the browser directly and switches the main panel to the top-up page.

Configuration File

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".

FieldDefaultDescription
appName"DeepSeek Harness"Display name
githubProxy.enabledfalseWhether the GitHub acceleration proxy is enabled
githubProxy.baseUrl""Proxy address, e.g. http://1.2.3.4:8080
githubToken""Optional GitHub API token
translate.enabledtrueWhether README translation is enabled
translate.endpointhttps://api-free.deepl.com/v2/translateDeepL endpoint
translate.apiKey""DeepL API key
translate.targetLang"ZH"Target language code (uppercase for DeepL)
translate.characterCount0Locally accumulated number of translated characters
translate.maxCharacters4000Maximum 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.

HTTP Routes

The host half exposes the following routes under the /dsh-desktop-settings/api prefix.

MethodPathDescription
GET/infoVersion, data directory, runtime directory, host state, and this plugin's id
GET/configReads the full configuration (internal path fields excluded)
POST/configDeep-merges a patch into the configuration and returns the result
POST/open-externalOpens a URL with the system default handler (http / https only)
POST/pick-directoryPicks a directory: the official directoryPicker first, then a PowerShell folder dialog
GET/restart-pendingWhether profile files were modified after the host became ready
POST/restart-hostDeliberately refuses, see below
GET/eventsIncremental fetch from the event queue, parameter since
POST/settings/test-translationDeepL connectivity self-test
POST/settings/test-github-proxyGitHub acceleration proxy connectivity self-test
POST/market/clearClears market cache, download leftovers, and translation cache
GET/recharge/statusTop-up page information and API key source
POST/recharge/openOpens the top-up page
GET/settings/inspect-homesDeliberately reports "not applicable", see below
POST/settings/import-fromDeliberately reports "not applicable", see below

The request body limit is 8MB; unknown routes return 404 with a JSON body.

Deliberately Unimplemented Capabilities

The three routes below exist but report a clear failure instead of being silently ignored or returning an empty result.

Cross-DSH_HOME detection and import

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.

Restarting the host

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".

Activation Criteria

HalfActivation criterion
Host halfThe package name appears in the profile's dsh.profile.bundles array; the host assembles the plugin tree at startup
Client halfdsh.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:

SlotContent
settings.sectionThe "桌面设置补充" section (order: 30)
sidebar.panellistThe "API 充值" sidebar entry (order: 44); clicking it opens the top-up page
mainThe 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.

Testing

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:

  • the host half can be imported and exports name / inject / apply;
  • the file referenced by dsh.bundle.patch in package.json really exists;
  • dsh.client.platform === 'web' and the file referenced by exports['./client'] really exists;
  • route alignment: every route path called from client.js is extracted and checked one by one against the route table in index.js;
  • the defaults in 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.

Known Limitations

  • You must register 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.
  • Whether the client half can reach the host can only be confirmed in a real window. The page origin has three possibilities, so the transport layer probes candidates in order; picking the wrong one leaves a "宿主基址 = …" line in the console.
  • This repository does not include the server-side reverse proxy configuration. The 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.
  • Self-hosted reverse proxies are usually plain HTTP. When 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.
  • The plugin cannot restart the host. After installing or changing configuration you must quit and reopen DeepSeek Harness manually.
  • Translation quota is approximated by a local counter. 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.
  • If the official implementation changes the profile structure or the 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.

License

MIT