dsh-desktop-settings
English | 简体中文
为 DeepSeek Harness (DSH) 桌面版补充官方设置中缺失的几节:README 翻译(DeepL)配置与
连通性自测、GitHub 加速代理(自建反向代理)配置与连通性自测、插件市场缓存清理,以及
DeepSeek 开放平台的充值入口。
本插件以独立 bundle 形式装入 DSH profile,不修改任何官方代码。
目录
问题背景
官方设置页已覆盖 account / general / models / plugins / subagent / web-search /
agent-loop / shell 等节,但以下几项没有对应入口:
| 缺失项 | 影响 |
|---|
| README 翻译 | 插件市场中大量仓库只有英文 README,中文用户不易判断用途 |
| GitHub 加速代理 | 直连 GitHub 常因 TLS 握手与首字节延迟而缓慢或失败,缺少统一的出口配置 |
| 缓存清理 | 市场列表缓存、下载残留与翻译缓存没有清理入口 |
| 充值入口 | 余额不足时需要自行查找充值页 |
本插件只补齐这四项。以下三节刻意不提供:「应用名称」「DSH 数据目录(DSH_HOME)」
「从其他 DSH 目录沿用配置」—— 它们的前提是外壳会划出一个完全隔离的数据目录,而官方
桌面版不隔离 ~/.dsh,只存在一个家目录,因此这三节没有存在的前提。
环境要求
| 项 | 要求 |
|---|
| Node.js | ^22.19.0 || >=24 |
| DeepSeek Harness | 官方桌面版(宿主半边依赖其 process.argv 布局与 profile 结构) |
宿主半边只依赖 ctx.webServer;客户端半边依赖官方客户端模块加载器与
@deepseek-ai/dsh-client-ui-slots 提供的槽位。
安装
用官方桌面版随包的 pnpm 装进目标 profile:
# 用官方桌面版随包的 pnpm 装进 profile
pnpm add "file:<插件目录绝对路径>"
# 或者直接从 GitHub 装
pnpm add "github:VCPr0j3k7/dsh-desktop-settings"
示例(路径按实际安装位置替换):
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/<用户名>/.dsh/profiles/desktop"
cd "$PROFILE"
"$NODE" "$PNPM" add "file:C:/path/to/dsh-desktop-settings"
装完还必须把包名加进 profile package.json 的 dsh.profile.bundles 数组,否则插件
不会加载 —— 这是本项目的关键坑:
{
"dsh": {
"profile": {
"bundles": [
// …已有条目…
"dsh-desktop-settings"
]
}
}
}
pnpm add 只会把包写进 dependencies 并安装到 node_modules。声明了
dsh.bundle.patch 的插件必须在 dsh.profile.bundles 中登记才会被挂载;仅安装而不登记时,
插件加载过程没有任何报错,界面上也看不出差异,只是它的设置分区与侧边栏入口都不会出现。
官方 CLI 的 dsh plugin add 会自动完成这一步,手动执行 pnpm 时需自行补齐。
安装完成后必须重启 DeepSeek Harness。宿主只在启动时装配插件树,运行中的实例不会
热更新。
验证是否生效:
-
重启后,设置页应出现「桌面设置补充」分区,侧边栏应出现「API 充值」入口;
-
打开开发者工具,控制台应出现:
[dsh-desktop-settings] client factory 已执行
[dsh-desktop-settings] 宿主基址 = (相对路径)
第一行说明客户端半边已被加载,第二行说明它已连上宿主。只有第一行而没有第二行,
表示客户端未能连上宿主。
卸载
cd "$PROFILE"
"$NODE" "$PNPM" remove dsh-desktop-settings
随后把 dsh.profile.bundles 中的 dsh-desktop-settings 条目一并删除,并重启
DeepSeek Harness。
若要临时停用而不卸载,只需从 dsh.profile.bundles 中移除该条目后重启。
工作原理
本插件由「两个半边」组成:
dsh-desktop-settings/
├── package.json 声明 dsh.bundle.patch 与 dsh.client.platform
├── cordis.patch.yml 宿主侧挂载声明
├── index.js 宿主半边:Cordis 插件,在 /dsh-desktop-settings/api 下注册路由
├── client.js 客户端半边:window.__ModuleLoader__.load({ id, factory })
└── host/ 适配层与服务实现
├── env.mjs 把 process.argv 解析成路径与宿主上下文
├── router.mjs HTTP 门面(前缀派发、{ok,data|error}、事件队列)
├── config.mjs 共享配置(<DSH_HOME>/dsh-extras.json)
├── system.mjs 打开浏览器 / 选择目录 / 打开文件夹
├── logger.mjs 输出捕获与日志环形缓冲
├── electron-shim.mjs app.getPath('userData') 的替身
├── yaml-libs.mjs 借用官方所使用的两个 YAML 库(多候选路径)
└── services/ 翻译、GitHub 代理与设置辅助能力
宿主半边
- 在
ctx.webServer 上注册一条 kind: 'prefix' 路由,前缀为
/dsh-desktop-settings/api,前缀内部的派发由 host/router.mjs 完成。注册点只有一处,
卸载时由框架统一清理。
- 所有响应统一为
{ ok: true, data } 或 { ok: false, error },业务失败也返回 HTTP 200;
状态码只用于表达「路由不存在」这类传输层事实。
- 进度事件进入环形队列(上限 400 条),客户端以
GET /events?since=<seq> 轮询增量。
不采用 SSE,是因为外壳对自定义 scheme 的转发是否会缓冲响应体无法确认,轮询没有这个
未知数。
客户端半边
- 页面由官方外壳提供,其来源(origin)有三种可能:宿主自身的 HTTP 地址、外壳的自定义
scheme(
dsh-app://app/)、不透明来源(location.origin === "null")。因此传输层在
第一次请求时按顺序尝试候选基址(相对路径 → http://dsh.internal),并以 /info 的
返回内容判定哪条真正可用。
- 判定条件是
data.plugin 等于本插件 id,而不是「返回 200」—— SPA 的兜底路由会把未知
路径也回成 200 加一段 HTML。
- 选定基址后记录,后续请求直接复用;全部候选都不可用时回落到相对路径,并只在控制台留下
一行提示,不反复重试。
功能与设置项
README 翻译(DeepL)
- 开关默认开启。翻译只对「基本不含中文」的文本调用:CJK 字符少于 30 个且占比低于 8% 时
才发送请求,因此真正的中文 README 不会消耗配额。
- 关键词提取完全在本地完成,不受翻译开关影响,离线可用。
- 翻译结果按仓库缓存在磁盘上,重复打开不会重复计费。
- 连通性自测:
POST /settings/test-translation 会翻译一句固定文本,返回检测到的源语言、
目标语言与译文。若返回 456 表示配额用尽,403 表示密钥无效或 Free / Pro 端点选错。
- DeepL Free 版密钥以
:fx 结尾,必须使用 api-free.deepl.com 主机;认证头是
Authorization: DeepL-Auth-Key <key>,不是 Bearer。
GitHub 加速代理
-
默认关闭且地址留空。代理地址属于搭建者本人,插件不预置任何值,也不会把使用者的请求
静默导到某台机器上。关闭时行为与没有该功能时完全一致。
-
开启条件是开关打开且地址非空;只开开关不填地址时静默回落到官方地址,而不是报错。
-
地址会被归一化:容忍未写协议、结尾多一个斜杠、反向代理挂在子路径下三种情况;协议只认
http / https,其它一律判为无效并回落直连。
-
路径前缀与上游域名一一对应,令牌、Accept 头与请求方法均原样透传:
| 上游域名 | 代理路径前缀 |
|---|
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 由本插件手动跟随(上限 6 跳),拿到 Location 后把其中的 GitHub 域名重新改写为
代理地址再请求。若交给 fetch 自动跟随,它会直接连接重定向目标域名,等于绕开代理。
-
连通性自测:POST /settings/test-github-proxy 打两个探针 —— 代理根路径与一条真实的
raw 请求(electron/electron 的 README)。两者都必须通过:根路径正常只说明反向代理
在运行,不代表它能连上 GitHub。
-
可选令牌 githubToken 默认留空。留空时按匿名请求发送(每小时 60 次);填入个人访问
令牌后走认证额度(每小时 5000 次)。令牌仅用于访问 GitHub API,不写入日志,也不随
插件分发。
清理缓存
POST /market/clear 删除以下内容:
| 目标 | 说明 |
|---|
<DSH_HOME>/plugin-data/market-cache.json | 插件市场列表缓存 |
<DSH_HOME>/plugin-data/market-downloads | 下载残留目录 |
<DSH_HOME>/plugin-data/translate-cache/ | 翻译缓存 |
单个目标删除失败时会被跳过,不会让整个操作失败。
API 充值
GET /recharge/status 返回充值页地址、用量页地址、API Key 的来源与当前数据目录。
- API Key 来源只做存在性探测,不读取也不回传密钥本体:优先看环境变量
DEEPSEEK_API_KEY,其次检查 <DSH_HOME>/.credentials.yaml 中是否存在该键。
POST /recharge/open 用系统默认浏览器打开 https://platform.deepseek.com/top_up。
打开地址前会校验协议,只放行 http / https。
- 侧边栏的「API 充值」图标点击后直接打开浏览器,同时主面板切到充值页。
配置文件
所有设置统一写入 <DSH_HOME>/dsh-extras.json,与插件市场(dsh-plugin-market)共用
同一份文件:写入方是本插件,读取方还包括需要翻译与下载的市场模块。采用文件而不是插件间
HTTP 调用,是因为文件不存在「另一个插件没装 / 还没启动」的时序问题。
| 字段 | 默认值 | 说明 |
|---|
appName | "DeepSeek Harness" | 显示名 |
githubProxy.enabled | false | 是否启用 GitHub 加速代理 |
githubProxy.baseUrl | "" | 代理地址,形如 http://1.2.3.4:8080 |
githubToken | "" | 可选的 GitHub API 令牌 |
translate.enabled | true | 是否启用 README 翻译 |
translate.endpoint | https://api-free.deepl.com/v2/translate | DeepL 端点 |
translate.apiKey | "" | DeepL API Key |
translate.targetLang | "ZH" | 目标语言代码(DeepL 使用大写) |
translate.characterCount | 0 | 本地累计的已翻译字符数 |
translate.maxCharacters | 4000 | 单个 README 最多翻译的字符数 |
配置读取不做缓存:文件只有几百字节,每次重读的成本可以忽略,换来的是「改完立刻生效」。
未知字段会原样保留,缺失字段回落到默认值。
首次运行时,若 <DSH_HOME>/dsh-extras.json 尚不存在,会尝试从
%APPDATA%/DSH-Desktop/config.json(或 DSH Desktop、dsh-desktop)迁移一次
translate 与 githubProxy,使此前填写过的密钥与代理地址可以继续使用。
HTTP 路由
宿主半边在 /dsh-desktop-settings/api 前缀下提供以下路由。
| 方法 | 路径 | 说明 |
|---|
| GET | /info | 版本、数据目录、运行时目录、宿主状态与本插件 id |
| GET | /config | 读取完整配置(不含内部路径字段) |
| POST | /config | 深合并写入配置,返回写后的完整配置 |
| POST | /open-external | 用系统默认程序打开网址(仅 http / https) |
| POST | /pick-directory | 选择目录:优先官方 directoryPicker,退回 PowerShell 文件夹对话框 |
| GET | /restart-pending | profile 关键文件是否晚于宿主就绪时刻被修改 |
| POST | /restart-host | 刻意拒绝,见下文 |
| GET | /events | 增量拉取事件队列,参数 since |
| POST | /settings/test-translation | DeepL 连通性自测 |
| POST | /settings/test-github-proxy | GitHub 加速代理连通性自测 |
| POST | /market/clear | 清理市场缓存、下载残留与翻译缓存 |
| GET | /recharge/status | 充值页信息与 API Key 来源 |
| POST | /recharge/open | 打开充值页 |
| GET | /settings/inspect-homes | 刻意报「不适用」,见下文 |
| POST | /settings/import-from | 刻意报「不适用」,见下文 |
请求体上限为 8MB;未知路由返回 404 与 JSON 响应体。
刻意不实现的能力
以下三条路由存在,但会明确回报失败,而不是静默忽略或返回空结果。
跨 DSH_HOME 的配置探测与导入
GET /settings/inspect-homes 与 POST /settings/import-from 对应「从另一个 DSH 数据目录
沿用配置」这一能力。它的前提是外壳划出完全隔离的数据目录,而官方桌面版不隔离 ~/.dsh,
只存在一个家目录,因此该能力不适用。两条路由会返回可读的原因说明,而不是 404 —— 这样在
有人手工构造请求时能直接得到解释。
host/services/settings-ops.mjs 中保留了该能力的完整实现,供需要隔离语义的环境使用。
重启宿主
POST /restart-host 会返回
插件无法重启宿主进程,请手动退出并重新打开 DeepSeek Harness。
在某些外壳中 DSH 是其 spawn 出来的子进程,因此可以重启;但插件本身就是宿主加载的一部分,
无法让宿主重启自己,而尝试杀死进程会一并丢失用户的会话。因此这里如实拒绝,由界面把
「请手动重启」显示给用户。
启用条件
| 半边 | 启用条件 |
|---|
| 宿主半边 | 包名出现在 profile package.json 的 dsh.profile.bundles 数组中;宿主在启动时装配插件树 |
| 客户端半边 | dsh.client.platform === 'web',且运行环境提供官方客户端模块加载器与 @deepseek-ai/dsh-client-ui-slots 槽位 |
客户端半边向以下槽位注册内容:
| 槽位 | 内容 |
|---|
settings.section | 「桌面设置补充」分区(order: 30) |
sidebar.panellist | 「API 充值」侧边栏入口(order: 44),点击即打开充值页 |
main | 充值页主面板 |
官方桌面版的插件库开关在保存时会重写 dsh.profile.bundles(内部调用
sanitizeProfile(...) 清理非官方 bundle)。若插件突然失效,应首先检查该条目是否仍然存在。
测试
npm test
# 等价于
node test/check.mjs
test/check.mjs 为离线自检,只使用 Node 内置模块,不依赖 DSH 运行时,覆盖:
- 宿主半边可被 import,且导出
name / inject / apply;
package.json 中 dsh.bundle.patch 指向的文件真实存在;
dsh.client.platform === 'web',且 exports['./client'] 指向的文件真实存在;
- 路由对齐:从
client.js 中提取全部被调用的路由路径,逐条确认在 index.js 的路由表
中都有实现;
host/config.mjs 的默认值包含 githubToken 字段。
端到端验证需要在真机上进行:安装并重启桌面版后,设置页应出现「桌面设置补充」分区,
侧边栏应出现「API 充值」入口,两个「测试连通性」按钮应能返回结果。
已知限制
- 安装后必须手动登记
dsh.profile.bundles。 手动执行 pnpm 安装时不会自动登记,漏掉
这一步的表现为插件完全静默 —— 无报错、无日志、界面上没有任何变化。
- 客户端半边能否连上宿主只能在真窗口里确认。 页面来源存在三种可能,因此传输层采用
按顺序探测的方式;选错时会在控制台留下「宿主基址 = …」一行。
- 本仓库不包含反向代理的服务端配置。 代码注释中引用的
deploy/ghproxy.conf 是
搭建反向代理时的 nginx 配置示例,未随本包分发。路径前缀与 location 必须与上表一致,
对不上不会报错,只会安静地返回 404。
- 自建反向代理多为明文 HTTP。 启用
githubToken 时 Authorization 头会经它明文发出,
开启前请确认链路可信;建议不要在启用代理的同时配置令牌。
- 插件无法重启宿主。 安装或修改配置后需要手动退出并重新打开 DeepSeek Harness。
- 翻译配额由本地计数近似统计。
translate.characterCount 只累加本插件发出的字符数,
与 DeepL 账户的实际用量可能不一致,仅用于在设置页显示大致规模。
- 官方若变更 profile 结构或
process.argv 布局,宿主半边的路径解析会失效。 相关解析
集中在 host/env.mjs,失效时表现为无法定位 profile 或随包 pnpm。
许可
MIT