# dsh-web-ui

> Registers a top-level menu item on the DSH settings page, grouping the enable toggles and configurations for the dsh-web-ui plugin family; also provides a loopback-only compatibility bridge for the rc.6 host.

## Metadata

- Author: [@zhu1090093659](https://github.com/zhu1090093659)
- Repo: <https://github.com/zhu1090093659/dsh-web-ui.git>
- GitHub: [zhu1090093659/dsh-web-ui](https://github.com/zhu1090093659/dsh-web-ui)
- Stars: 5,128
- Language: TypeScript
- License: [Apache-2.0](https://spdx.org/licenses/Apache-2.0.html)
- Homepage: <https://gallery.dsh-market.com>
- Topics: `deepseek-harness`, `dsh`, `dsh-plugin`, `web-ui`
- Forks: 311
- Open Issues: 51
- Last push: 2026-08-20T14:37:38.000Z
- Added: 2026-08-20T00:00:00.000Z

## Install

```bash
dsh plugin --profile web add github:zhu1090093659/dsh-web-ui/packages/dsh-web-ui-settings
```

## Wiki

## 一句话定位
在 DSH 设置页注册一个一级菜单项「Web UI 插件」，把 dsh-web-ui 全家桶插件（task-board、remote-web-ui、describe-image 等）的启用开关和配置表单归组在同一处，同时为 DSH 0.1.0-rc.6 宿主提供仅 loopback 的兼容桥，让官方 apiproxy 不放行的第三方设置命名空间也能被全家桶卡片正常读写。

## 核心能力
- 在 DSH 设置页注册一个一级菜单项（与通用设置 / 模式 / 插件 / Agent 预设同级），标题与描述随语言切换
- 归组全家桶插件的配置卡片：各卡片默认折叠，独立展开后展示启用开关与配置表单，子槽 `web-ui.plugin.item` 由全家桶插件自行注册
- 提供 rc.6 兼容设置桥接：通过 `/api/dsh-web-ui-settings` 一对同源 loopback HTTP 接口重新暴露全家桶设置命名空间，绕过 DSH 0.1.0-rc.6 宿主 apiproxy 对第三方命名空间的硬编码拒绝
- 跟随用户在 `settings.yaml` 的 `web_settings_namespaces` 控制桥接开放范围；未配置时回退到内置全家桶 fallback 列表，配置后取与已注册命名空间的交集，未注册项永远不暴露
- 可选的认证反向代理访问模式：可显式放行同 Host 的精确 authority，把共享 token 交给上游代理注入，浏览器永远拿不到 token
- 与皮肤中心、社区插件、桌面宠物等其它一级设置分区并列，各自注册自己的菜单项

## 技术实现
- **语言**: TypeScript（ES Module，`type: "module"`）+ React 18（TSX）
- **关键依赖**: `schemastery ^3.18.0`（Host 配置 schema 校验）、`@deepseek-ai/dsh-host-webserver`（Host 半区 HTTP 路由）、`@deepseek-ai/dsh-settings`（Host 设置 seam 与 schema 校验/版本号机制）、`@deepseek-ai/dsh-client-ui-settings` + `@deepseek-ai/dsh-client-runtime`（浏览器半区设置面与 slot）、`react ^18.2.0`（peerDependency，UI 渲染）
- **架构模式**: 典型 cordis 双面插件（host + client 半区分层），通过 `cordis.patch.yml` 以 `id: ui-web-ui-settings` 插入 web profile bundle；Host 半区在 `ctx.settings` seam 内挂载 loopback-only HTTP 桥接路由（`/api/dsh-web-ui-settings/describe`、`/mutate`），浏览器半区先尝试官方 settingsScope，官方显示 unavailable 时回退到桥接控制器（rc.6 兼容路径），rc.7+ 宿主由 apiproxy 直接服务则桥接永不激活
- **入口文件**: `src/index.ts`（Host 半区入口，桥接路由挂载）、`src/client/index.ts`（浏览器半区入口，一级菜单项与 locale 注册）、`src/bridge.ts`（loopback-only HTTP 路由与认证代理门控）、`src/client/compat-settings-scope.ts`（rc.6 兼容 binder，把官方 settingsScope 与桥接控制器包成一个统一接口）

## 适用场景
同时装了多个 dsh-web-ui 全家桶插件（task-board、remote-web-ui、describe-image 等），又希望它们集中在 DSH 设置页同一处管理的人；或者运行在 DSH 0.1.0-rc.6 / rc.7 宿主上、没有这个兼容桥时全家桶插件卡片只能显示「namespace 不可用」说明。如果你只装了一个全家桶插件或者已经在 rc.7+ 宿主上让 apiproxy 直接服务设置面，这个分组入口带来的收益就很有限。

## 前置依赖与兼容性
| 依赖 | 最低版本 | 说明 |
|---|---|---|
| DSH（@deepseek-ai/dsh-client-runtime / dsh-client-connection / dsh-client-ui-settings / dsh-host-webserver / dsh-settings / cordis） | 0.1.0-rc.6+ | 与 rc.6 / rc.7 兼容，devDependencies 钉到 ^0.1.0-rc.8 |
| Node | ^22.19 或 >=24 | 来自 monorepo `packages/AGENTS.md` 约定，本包未声明 `engines` |
| React | >=18.2.0 | peerDependency，浏览器半区构建需要 |
| 平台 | macOS / Windows / Linux | 跨平台，桥接只走标准 HTTP 路由，没有平台相关依赖 |
| 原生模块 | 无 | 仅使用 `node:fs` / `node:os` / `node:path` / `node:crypto` 等内置模块 |

## 安装方式
```bash
dsh plugin --profile web add github:zhu1090093659/dsh-web-ui/packages/dsh-web-ui-settings
```

## 配置项
| 配置 | 类型 | 说明 | 默认值 |
|---|---|---|---|
| `trustedProxyHosts` | string[] | 通过已认证反向代理放行的精确 `host[:port]` 列表；空数组 = 仅 DSH loopback origin 直连，桥接不开放给任何代理 | `[]` |
| `proxyTokenEnv` | string | 保存反向代理共享 token 的环境变量名；token 本身不会被写入插件配置，未设置或为空时启用 `trustedProxyHosts` 会抛错 | `DSH_WEB_UI_SETTINGS_PROXY_TOKEN` |

另外，`settings.yaml` 里的 `web_settings_namespaces`（可选）决定桥接开放哪些全家桶命名空间；未配置时使用内置全家桶 fallback 列表，配置后每次桥接调用都会重新读取（不需要重启 DSH）；插件自身配置（`trustedProxyHosts` / `proxyTokenEnv`）修改后需要重启 DSH 才生效。

## 常见问题

**Q: 这个插件具体是干什么的？跟 task-board / remote-web-ui / describe-image 是什么关系？**

A: 它本身不实现这些功能，而是把它们在 DSH 设置页的启用开关和配置表单集中到一个一级菜单项「Web UI 插件」里，与通用设置 / 模式 / 插件 / Agent 预设同级。皮肤中心、社区插件、桌面宠物各自是独立插件包，也注册各自的一级菜单项并默认展开。

**Q: 为什么不装它也能用其它全家桶插件？**

A: 其它全家桶插件可以独立运行，只是少了设置页的分组入口。但 DSH 0.1.0-rc.6 宿主的 apiproxy 默认只放行它硬编码的 `WEB_SETTINGS_NAMESPACES` + 产品命名空间，所有第三方设置命名空间会被一律拒掉——没有这个兼容桥时，全家桶插件卡片就只能渲染一段「namespace 不可用」的说明文本。

**Q: 安装后启动报错 "Failed to load plugins ... keyed slot `settings.plugin.item` requires options.key" 怎么办？**

A: 0.1.17 及更早版本把组卡片注册到 keyed 槽 `settings.plugin.item` 时传的是 `id` 而不是必填的 `key`，DSH 0.1.0-rc.6 起在 loader entry 应用阶段会直接拒绝这种注册。修复办法：把 profile `package.json` 里所有 `@linxin666/*` 依赖升到 `^0.2.0`（至少 `^0.1.18`），重新 `pnpm install`；Windows 下重建陈旧的 `node_modules/@linxin666/*` junction 链接（先 `cmd /c rmdir <链接>` 再 `cmd /c mklink /J <链接> <目标>`），重启 `dsh web`。参考 issue #513。

**Q: 想通过反向代理对外暴露怎么配？**

A: 把 DSH Web 绑定到 loopback，在 cordis.patch.yml 里给 `trustedProxyHosts` 填好精确 authority，把高熵 token 放进 `proxyTokenEnv` 指向的环境变量里（DSH 和反向代理都要设置），让反向代理完成认证后用服务端注入的 token 替换请求头 `X-Dsh-Web-Ui-Settings-Proxy-Token`（不要透传客户端送来的）；Caddy 示例：`header_up X-Dsh-Web-Ui-Settings-Proxy-Token {$DSH_WEB_UI_SETTINGS_PROXY_TOKEN}`。带值的 `header_up` 会覆盖客户端值，不要同时再删除同一字段（Caddy 2.6 会先设置后删除）。`trustedProxyHosts` 修改后必须重启 DSH 才生效。

**Q: `web_settings_namespaces` 起什么作用？**

A: 它写在 `settings.yaml` 里，用来控制桥接开放哪些全家桶命名空间。未配置时使用内置全家桶 fallback 列表（task-board、remote-web-ui、describe-image、dsh-ssh、pet、skin-background、community-plugins、desktop-launcher 等）；配置后取与已注册命名空间的交集，未注册项永远不暴露。每次桥接调用都会重新读取，不需要重启 DSH。

**Q: 设置页看不到「Web UI 插件」菜单项怎么办？**

A: 仅当依赖的 `@deepseek-ai/dsh-client-ui-settings` 存在时该菜单项才会出现。检查 DSH 是否兼容（0.1.0-rc.6 起）、是否已安装完成 `pnpm install`、是否重启 `dsh web`；如果 profile 是从更早版本迁移过来的，按上面 FAQ「Failed to load plugins」的步骤升级依赖。

**Q: 桥接会暴露凭据、本机路径或其它 DSH 特权 API 吗？**

A: 不会。桥接只开放已注册全家桶命名空间与 `web_settings_namespaces` 的交集，schema 走 Host seam 的官方校验与持久化；不开放凭据、本机路径或任何其它 DSH 特权 API。认证代理模式下，token 由反向代理注入上游，浏览器永远拿不到 token。

**Q: 如何完全卸载？**

A: `dsh plugin --profile web remove @linxin666/dsh-client-ui-web-ui-settings`，然后重启 `dsh web`。该包不写入任何持久化设置，不会自动删除 `settings.yaml` 里的 `web_settings_namespaces` 条目。

## 上手难度
入门 — 装好之后默认仅 loopback、无需任何配置就能在设置页出现菜单项；只有需要走反向代理或限制 `web_settings_namespaces` 白名单时才需要读 README 的反向代理章节，理解 DSH 监听/认证头注入/`header_up` 替换顺序这几件事即可。

## 已知问题与限制
- 设置页菜单项仅在依赖的 `@deepseek-ai/dsh-client-ui-settings` 存在时才会出现；缺少该依赖时不会报错，只是不显示分区
- 桥接只服务 dsh-web-ui 全家桶设置，不会让 DSH 官方设置或凭据平面可被远程访问
- 认证代理模式本身不提供认证：没有正确配置并排序认证代理的部署必须让 `trustedProxyHosts` 保持为空，否则会出现「启用了 token 但请求不带 token」或「客户端伪造 token 绕过认证」等风险
- 插件配置（`trustedProxyHosts` / `proxyTokenEnv`）修改后必须重启 DSH 才生效；`web_settings_namespaces` 每次桥接调用重新读取，不需要重启
- 0.1.17 及更早版本在 DSH 0.1.0-rc.6+ 上会触发 "Failed to load plugins" 启动失败；profile `package.json` 里的 `@linxin666/*` 依赖必须升到 `^0.2.0`（至少 `^0.1.18`），Windows 下还需重建 junction 链接
- 通过 npm 与通过仓库 link 同时安装同一个包时，Host 半区由 `mountOnce` 保证只跑一次（共享同一 globalThis symbol），浏览器半区由官方 client 模块系统按包名去重；不会出现重复注册路由或重复设置命名空间
- Caddy systemd 单元若以 `caddy run --environ` 启动，会在启动时打印 `proxyTokenEnv` 指向的环境变量；请去掉 `--environ` 或严格保护其输出，避免 token 泄露到日志

---

This document is auto-generated by [deepseek-plugin.org](https://deepseek-plugin.org). HTML page: [dsh-web-ui](https://deepseek-plugin.org/plugins/zhu1090093659/dsh-web-ui/packages/dsh-web-ui-settings)
Wiki generated by AI (model: `MiniMax-M3`)
