# 工作台开发规范

版本：2026-10-08 · 以官网版本为准，DSH Desktop 内附文档副本供离线取用。

官方地址：https://dshdesktop.com/workbench/docs/development/

Markdown 原文（供 Agent 读取）：https://dshdesktop.com/workbench/docs/development.md

六步速览：https://dshdesktop.com/workbench/docs/quickstart/ （[Markdown 原文](https://dshdesktop.com/workbench/docs/quickstart.md)）。

本文是开发工作台时由 AI 直接执行的规范。**只在本地开发、自己使用时，满足本文即可**：按第 3 节写好包，装到本机 DSH Desktop，按第 8 节自测通过，就可以正常使用，不需要公开代码，也不需要上传到任何地方。

如果之后想把工作台上架到工作台市场，再阅读《工作台市场验收规范》：https://dshdesktop.com/workbench/docs/market-acceptance/ 。仓库公开、发布、简介、截图和收录 PR 的要求都在那一份里，本文不涉及。

工作台是一种 DSH 插件。它首先必须是一个合格的 DSH 插件包，能被 `dsh plugin add` 安装、被 Harness 加载；在此基础上，再向 Desktop 注册业务面板，并遵守工作台与会话、工作区的交互规则。

规则分三级：**必须**（不满足就不能安装或加载）、**建议**（强烈推荐，不满足会影响体验或以后上架）、**可选**。

## 1. 执行原则

**需求确认闸门（先于写代码）**：先确认用户要开发的工作台的业务场景、目标用户和最小核心流程。复制 Desktop 的通用“开发指令”、打开了某个目录、目录里已有项目或依赖，均不能替代用户对本次开发目标的说明；不要仅凭目录名或现有代码替用户选定产品方向。若本次请求和用户已确认的上下文仍不足以明确这三项，用**一个合并问题**询问，例如：“这个工作台要服务谁、解决什么业务场景？用户从进入到完成任务的核心步骤是什么？”**等待用户答复，停在需求确认阶段**；此前可以只读检查目录、版本、脚本和宿主能力，不得创建或修改业务代码、界面，不得运行构建、打包、安装或提交投稿。空目录不能被当作已确认需求；代理收到通用开发指令后直接开工属于执行失误。

需求明确后，从项目读取名称、作者、版本、说明和测试命令，只询问无法查明且会影响实现的必要信息。开发请求本身不代表授权公开代码、商业数据或凭证。

开发前必须检查：

- 确认项目根目录、已有代码、未提交修改、包管理器、脚本和目标 Desktop 版本；不要覆盖别人的修改。
- 确认目标平台与架构，区分 JS/TS 包、外部程序和原生依赖。缺少宿主或工具链时明确说明，不能只凭构建通过就说能运行。

### 一页执行流程

1. **确认需求**：从用户本次请求及已确认的上下文记录业务场景、目标用户、用户完成一次任务的核心流程。通用开发指令或项目目录本身不算确认；三项不明确时执行上述闸门，等待答复。
2. **检查项目**：确定项目根目录，运行 `pwd`、`git status --short`（若是 Git 仓库），查看已有代码、包管理器、未提交改动和目标 Desktop 版本；保留无关改动。
3. **确定最小业务流程与布局**：写下入口、主要动作、完成状态及失败恢复；按第 4 节明确选择标准分栏或 `customFrame`、首次无会话时的入口，再决定所需宿主能力。
4. **实现**：按第 3 节建立插件包和注册；第 4–7 节按责任及实际使用的能力落实。
5. **包校验**：运行 `pnpm pack`，检查 tarball 内 `package.json`、声明的入口和 `cordis.patch.yml`，检查大小和敏感文件。
6. **本机安装实测**：按 3.6 探测当前 Desktop 的安装能力、安装该 tarball、完成所需重启，并按第 8 节记录真实 UI 与加载证据。在目标 DSH Desktop 内开发时，按下方“同机重启边界”交给用户手动重启。

**同机重启边界**：如果 Agent 正运行在需要重启的这台 DSH Desktop 会话内，安装后不要自行关闭、重启 DSH Desktop 或 Harness，也不要用 `ps`、`pgrep` 等进程探测来尝试管理这次重启。告知用户完整退出并重新打开 DSH Desktop；在用户回来确认已重开后，才继续检查插件加载、已安装列表、左侧入口和真实界面。此前将这些重启后的验收项标为**待验证**，不能宣称已通过。运行在目标应用之外的开发 Agent 可按正常重启流程操作。

**空目录演练**：若目录为空、用户只粘贴 Desktop 的“开发指令”或只说“做个工作台”，此时第 1 步尚未通过。Agent 应先提出合并问题：“这个工作台要服务谁、解决什么业务场景？用户从进入到完成任务的核心步骤是什么？”可以运行 `pwd`、查看目录及 Desktop 能力；在用户答复前，流程停在第 1 步，不创建 `package.json`、组件或业务页面，也不构建、安装或投稿。

## 2. 工作台是什么

一个工作台包同时是：

- **一个 npm 包**：有 `name`、`version`，可以从本地目录或 `.tgz` 文件安装，上架后也可以从 npm 或 GitHub 安装。
- **一个 DSH 插件（bundle）**：`package.json` 声明 `dsh.bundle.patch`，由 `cordis.patch.yml` 把服务端入口插入 Harness 的插件树。
- **一个 Desktop 工作台**：客户端通过 `desktopWorkbenches.register()` 注册业务面板。Desktop 从 `repository`、安装记录和市场分发映射解析仓库身份。

同一个工作台有三个标识，职责不同，不能混用：

| 标识 | 在哪里 | 用途 |
|---|---|---|
| npm 包名 | `package.json` 的 `name` | 安装、依赖解析、`cordis.patch.yml` 的 `name:` |
| 插件条目 id | `cordis.patch.yml` 中 `insert` 行的 `id` | Harness 插件树中定位这一行 |
| 工作台身份 | 规范的 GitHub `repository` URL、市场条目 `id` | 小写 `owner/repository`；市场卡片、侧栏入口和会话归属共用 |
| 兼容用旧 ID | 老包 `register({ id })`、旧目录 `workbenchId` | 仅迁移旧数据时使用；新包和新投稿都不填写 |

Desktop 将 GitHub owner 和 repo 规范化为小写，例如 `https://github.com/Owner/Repo` 对应 `owner/repo`。仓库改名或转移会改变工作台身份，需按迁移规则处理原有会话，不能仅改 URL。旧版 `wb-owner-repo` 只作为兼容映射，不是新包的注册 ID。

## 3. 包格式

### 3.1 目录结构

一个包只放一个工作台。典型结构：

```text
package.json          # npm 与 DSH 插件声明
cordis.patch.yml      # 把服务端入口插入插件树
dsh/index.js          # 服务端入口（exports["."]）
lib/client.js         # 已构建的客户端入口（exports["./client"]）
README.md  LICENSE
```

### 3.2 package.json

| 字段 | 级别 | 要求 |
|---|---|---|
| `name`、`version` | 必须 | 普通 npm 字段，`version` 为完整 SemVer（如 `1.2.0`） |
| `"type": "module"` | 建议 | 所有官方示例都使用 ES 模块 |
| `main` 与 `exports["."]` | 必须 | 指向服务端入口，`cordis.patch.yml` 导入的就是它 |
| `dsh.bundle.patch` | 必须 | 例如 `"dsh": { "bundle": { "patch": "./cordis.patch.yml" } }`，路径相对包根目录。没有它，包只会被当作普通依赖安装，不会激活任何插件层 |
| `dsh.client.platform` | 必须 | 固定为 `"web"`，否则客户端模块不会被加载 |
| `dsh.client.inject` | 必须 | 字符串数组，**必须包含 `dsh-desktop-workbenches`**，保证工作台服务先于你的客户端加载；用到其他客户端包时一并列出。这里只决定加载顺序，不是 Cordis 服务注入 |
| `exports["./client"]` | 必须 | 声明了 `dsh.client` 就必须导出已构建的客户端文件，否则启动时报错 |
| `dsh.client.external` | 可选 | 客户端需要 `require` 共享基线以外的模块（包括子路径）时列出；不能列自己 |
| `exports["./package.json"]`、`exports["./cordis.patch.yml"]` | 建议 | 与官方包保持一致 |
| `files` | 建议 | 必须把 `cordis.patch.yml`、服务端入口和构建产物包含进去 |
| `peerDependencies` | 建议 | 官方 `@deepseek-ai/*` 包（如 `@deepseek-ai/cordis`、`@deepseek-ai/dsh-client-connection`）写在 `peerDependencies`，不要写在 `dependencies`，避免装进第二份宿主代码。版本范围要显式包含预发布分支，例如 `">=0.1.5-rc.2 <0.2.0-0"`，否则预发布版的 Harness 会被排除，安装时报 `ERESOLVE` |
| `@deepseek-ai/schemastery` | 建议 | 服务端 `Config` 使用它定义；它是运行时依赖 |
| `license`、`author`、`description` | 建议 | 真实填写 |

不要依赖未文档化的 `dsh` 字段（例如 `dsh.client.inline`），它们会被静默忽略。

### 3.3 cordis.patch.yml

文件是一个 YAML 数组。工作台通常只需要一条 `insert`：

```yaml
- insert:
    - id: my-workbench            # 插件条目 id
      name: my-workbench-package  # 必须写 npm 包名，Node 才能解析到安装的代码
      config:
        root: !!js dshHomePath('my-workbench')   # 可选：业务数据目录
```

- **必须**：`name` 写包名，不写相对路径或绝对路径。
- 空文件或只有注释的文件会导致启动失败；不需要任何行时写 `[]`。
- `!!js` 表达式在启动时求值，可用 `dshHomePath()` 得到 DSH 数据目录下的路径。
- 覆盖已有条目时，patch 会**整体替换**该行的 `config`，不会合并，需要保留的键要全部写上。工作台不应覆盖 Desktop 或其他插件的条目。

### 3.4 服务端入口

```js
import Schema from '@deepseek-ai/schemastery'

export const name = 'my-workbench'
export const inject = ['connection']
export const Config = Schema.object({ root: Schema.string().required() })

export function apply(ctx, config) {
  // 注册本工作台自己的本机接口，例如 /api/my-workbench/...
}
```

- `Config` **必须**是 Schemastery schema，不能直接导出普通对象。
- 本机接口放在自己的路径前缀下（如 `/api/<工作台 id>/`），不能占用 Desktop 或其他插件的路径。
- 需要判断会话归属时，注入内部只读服务 `desktopWorkbenchOwnership` 并调用 `await read()`；不要从可见面板、旧预设或私有设置推断归属。
- 业务数据写在配置的数据目录或作者文档说明的位置；卸载工作台时不得删除用户数据。

### 3.5 客户端入口

客户端文件**必须**是已构建好的单文件模块，通过模块加载器注册：

```js
window.__ModuleLoader__.load({
  id: 'my-workbench-package',
  factory: (require) => {
    const React = require('react')
    function BusinessPanel({ service, entry }) { /* 业务面板 */ }
    function apply(ctx) {
      ctx.effect(() => ctx.desktopWorkbenches.register({
        title: '我的工作台',
        repository: 'https://github.com/owner/repository',
        description: '说明它解决什么问题'
      }, BusinessPanel))
    }
    return { apply, inject: ['desktopWorkbenches'] }
  }
})
```

- `require` 只能拿到共享基线（React、Cordis 等）、已加载的插件和 `dsh.client.external` 中列出的模块。其他依赖要在构建时打进客户端文件。
- `register()` **必须**提供 `title`、业务组件和规范的 GitHub `repository` URL。新包**不要填写** `register({ id })`；Desktop 用 `repository` 解析 `owner/repo`，并用调用方客户端包、市场分发或安装记录交叉核对，来源冲突时不会暴露 provider。旧包的 `id` 仅用于迁移，不是新协议。
- `workbench.json` 和投稿 YAML 的 `workbenchId` 不是新工作台的必填协议；运行时包名必须与 `window.__ModuleLoader__.load({ id })` 对应。
- 可选注册字段：`description`、`panelTitle`、`icon`、`audience`、`requirements`、`layout`（`businessSide` 为 `left` 或 `right`，`businessWidth` 在 0.25–0.7 之间）、`customFrame`（布尔值，见第 4 节）、`initialization`。
- 注册返回的函数用于注销，要交给 `ctx.effect` 管理。

### 3.6 构建与本地安装

- 客户端文件**必须**是构建好的单文件，Desktop 不会替你构建。
- **先探测能力，不猜版本号**：记录 Desktop “关于”中的版本、平台和架构；在“工作台管理／已安装的工作台”查找本地安装入口，再核对当前版本随附的插件安装界面或 `dsh plugin add --help` 是否接受本地 `.tgz`。只有界面或命令明确支持该输入时才安装；不能从官网文档的发布日期推断当前安装版本支持。记录采用的入口与输出。
- 用已确认支持的入口安装 `pnpm pack` 得到的 `.tgz`。CLI 路径按其 `--help` 显示的语法执行；安装后按界面提示完成重启，回到“已安装的工作台”确认条目，再从左侧入口实际打开并检查业务面板。如果 Agent 正在目标 DSH Desktop 内运行，必须遵守第 1 节“同机重启边界”，由用户完整退出并重新打开应用。`dsh --profile web --dump-config` 出现插件层只能证明配置，不能替代 UI 加载证据。
- 当前 Desktop 无本地包安装入口、CLI 不支持或安装失败时，交付已校验的 `.tgz` 与包内容/校验记录，写明 Desktop 版本、探测结果和失败原因；将“安装、重启后加载、已安装列表、左侧入口、业务操作”逐项列为**未实测**，不能宣称本机验收通过。
- 需要运行安装或构建脚本时（pnpm 10 默认阻止），要明确知道每个脚本做什么，再单独授权。

## 4. 界面边界

工作台是一种面向具体工作场景的插件。本规范约束工作台、会话、工作区的关联关系和切换行为，不规定统一的业务面板设计。

工作台作者或用户可以定义业务面板的布局、内容、工具栏和业务操作。接入已有工作台时，优先复用原有界面和业务绑定流程，不要求加入统一顶部工具栏、会话下拉框、业务区折叠按钮或“通用聊天”按钮。

### 4.1 先按主任务选择布局

- **标准分栏**：默认左侧是宿主原生会话，右侧是业务面板，占主内容区的 36%。可在 `register()` 的 `layout` 中指定 `businessSide: 'left' | 'right'` 和 `businessWidth: 0.25–0.7`；这是业务区所占比例，不保证业务组件的每一列都有足够空间。它适合状态、工具、资料等紧凑的会话伴随面板。首次没有当前会话时，宿主会在会话区域显示通用的“开始使用”引导；业务面板仍须可用，并检查两侧内容是否形成合理的首次使用流程。不要仅靠调大 `businessWidth` 把报表塞进分栏，导致另一侧引导或会话被挤压。
- **`customFrame: true`**：当阅读报表、操作看板或宽表格本身是主任务时，可让工作台编排整个宿主主内容区。需要原生会话时，把宿主传入的 `conversation` 节点放到合适的位置，不自行复制聊天界面。此模式不显示标准分栏的宿主通用无会话引导；工作台需自行提供首次进入、无会话以及会话相关操作的入口，明确何时创建或恢复工作台会话。若业务主要是伴随对话的小工具，仍可选择标准分栏，不要求所有工作台改为整页。

无论选择哪种布局，都要按**业务内容容器的实际宽度**设计响应式布局；整窗媒体查询不能代表侧栏展开、分栏及嵌入后的可用宽度。可用容器查询或测量业务根容器，在空间不足时让多列内容重新排布、折叠次要信息或允许有界滚动；长中文标题、英文标识、数字和操作按钮均应保持可读可用，避免单字竖排、按钮裁切和整页横向溢出。

市场、工作台切换和设置等公共入口由 DSH 保留，业务面板不得遮挡。使用 `customFrame`（见 3.5）的工作台必须限制在宿主分配的主内容区域内：根容器应按正常 flex 布局填满该区域；内部使用绝对定位、窗口最大化或拖拽换位时，都不得越过宿主容器的定位与裁剪边界，也不得覆盖 Desktop 左侧会话栏。原生会话由宿主提供的 `conversation` 节点放进业务布局，不能再创建一套聊天界面。原生会话能力与模式切换沿用 Desktop 的规则。

macOS 收起侧边栏后，窗口按钮与侧边栏展开按钮占用主内容区左上角。宿主会给左侧嵌入式业务面板和 `customFrame` 的根节点内、作为首个子元素的语义 `<header>` 预留 `--dsh-frame-leading-clearance` 宽度；工具栏可留在原来的 48px 顶部行，主体无需整体下移。自定义工具栏若不是这个结构，应在自身样式中使用同一变量避让，不要覆盖窗口按钮或 `shell.leading`。

## 5. 工作台、会话与工作区

- 一个工作台可以关联多个会话，每个会话最多归属一个工作台。
- 工作区是 DSH Desktop 的项目资料环境；每个会话如绑定工作区，最多绑定一个工作区。同一工作区内的不同会话可以属于不同工作台。
- 一个工作台可以处理多个工作区，不同工作台也可以使用同一个工作区。
- 工作台内部的档案、业务项目不等同于 DSH 工作区，不要求重复绑定。例如，玄学档案和选址项目继续使用各自原有的创建、选择流程。DSH 工作区目录是用户资料位置，不因打开或创建业务项目而自动归属某个工作台。

## 6. 未绑定会话时的使用

打开工作台后，即使没有会话或工作区，也应立即显示业务面板。浏览、创建、选择业务档案或项目，不得以已有原生会话或工作区为前提。

仅当用户执行向 Agent 发起请求、写入会话草稿等依赖会话的操作时，才检查当前会话是否属于该工作台。尚未绑定时，在该操作处引导创建或打开会话，不阻断其他业务功能。

工作台内部应提供“在此工作区新建工作台会话”或“选择资料位置并开始工作台会话”等明确动作。业务项目本身不需要文件系统时可以先创建业务记录；一旦需要保存文件或创建 DSH 工作区，必须先调用宿主目录选择器，由用户选择已有目录或在系统选择器中明确新建目录。工作台不得在默认位置、用户主目录、当前仓库或其他推测路径中静默创建文件夹。确认位置后再创建工作区和会话、登记归属、保存业务关联并填入开场草稿；取消时不得留下目录、空项目、工作区、会话或绑定。已有业务资料只能恢复用户此前确认的位置；位置不存在时重新选择，不得自动创建同名目录。失败时保留可重试状态，不得默默改用无关工作区。

先选择业务档案或项目，再创建第一条会话时，应保留当前业务选择。打开已有会话时，优先恢复该会话已有的业务映射，不被其他会话最近选择覆盖。

接入已有工作台时保留原有的项目创建引导和独立业务流程。用户无需先手工新建会话；工作台通过 Desktop 接口发起创建或恢复，由宿主校验并登记归属。开场草稿保留用户已输入的内容并由用户发送；创建失败或延迟时保留草稿供重试。自动执行的业务流程必须在正确归属的会话中运行，不能与开场草稿混用，也不能由隐藏工作台触发误发送。

## 7. 模式入口与切换

宿主在侧栏顶部提供单一模式切换器：按钮只显示当前模式，展开后可在“原生会话”和已添加工作台之间切换；“工作台管理”入口常驻最右侧。工作台不得复制、遮挡或替换这些公共入口。切换模式只改变前台展示和会话筛选，不执行卸载重装，也不停止后台任务。

“原生会话”和各工作台是平行模式，当前模式决定可见会话和新建归属：

- **工作台模式**：只显示当前工作台的会话。“新建会话”或切换工作区时，只能打开当前 owner 的会话；目标工作区没有时创建新的 owner 会话，并在显示前持久化归属和最近会话。不得复用、收编或改绑普通会话及其他工作台会话。
- **原生会话模式**：只显示未绑定会话。“新建会话”或切换工作区时，只能打开未绑定会话；目标工作区没有时创建一条。不得根据同一工作区中的工作台会话推断或切换工作台。

显式打开未绑定会话时切换到“原生会话”；显式打开已绑定且 owner 可用的会话时切换到对应工作台。owner 已移除、卸载或不可用时按普通会话处理，不自动唤起或重装。异步创建期间如果用户继续导航、切换模式或移除 owner，不得把界面拉回旧模式、覆盖后续选择或绑定到失效 owner。

切换模式不得自动收起或展开左侧会话栏；侧栏状态由用户控制并在模式间保持。工作台会话标题前显示所属工作台的小图标，普通会话不显示；图标只表示归属，并提供可访问名称或提示。折叠态与展开态中的按钮、菜单、选中态和焦点轮廓都不得裁切、重叠或溢出。

首次进入工作台可以显示首页、空会话状态或按工作台约定初始化会话，但业务面板不能依赖会话初始化完成。后续进入恢复该工作台最近会话。导航和切换不得删除会话、业务资料、项目文件、收藏或笔记。

## 8. 本地自测清单

开发完成后按责任逐项确认。“宿主负责”是对当前 Desktop 能力的观察和问题反馈，插件作者不得为此复制宿主界面；标为“仅使用该能力时适用”的项，不使用时记“不适用”及原因。全部适用项有证据通过，才能称为本机自测通过。

**包与加载**

- [ ] **插件负责**：第 3 节全部“必须”项通过。运行 `pnpm pack`，用 `tar -tzf <产物.tgz>` 核对 `package.json`、服务端和客户端入口、`cordis.patch.yml`；从该产物安装，而非只从源码启动。
- [ ] **插件负责**：按 3.6 安装并完成所需重启；若 Agent 在目标 DSH Desktop 内运行，由用户手动完整退出并重新打开应用，用户确认后再验收。`dsh --profile web --dump-config` 中找到本包插件层，在 Desktop“工作台管理／已安装的工作台”找到条目，再从左侧入口打开业务面板。分别记录配置、列表和真实界面证据；重启前这些加载与界面项目保持待验证。

**第 4 节：界面边界**

- [ ] **插件负责**：打开业务面板，缩放窗口并切换原生会话/工作台，确认面板不遮挡侧栏、市场和管理入口；键盘可到达业务操作。
- [ ] **插件负责**：分别在首次进入且无工作台会话、打开已关联会话时，检查业务入口与会话相关操作。标准分栏要检查宿主通用引导与业务面板并列时的阅读和操作；`customFrame` 要检查自己提供的首次使用和无会话入口。
- [ ] **插件负责**：在侧栏展开、收起及较窄窗口下，用真实长度的中英文标题、标识、数字和按钮检查业务区；不得出现单字竖排、关键控件裁切或整页横向溢出。按业务容器宽度验证多列内容的重排，而非只按整窗宽度判断。
- [ ] **仅使用该能力时适用，插件负责**：若使用 `customFrame`，缩窄窗口并操作布局、弹层和拖拽，确认都在宿主主内容区域内；使用原生会话时确认宿主提供的 `conversation` 节点可用。

**第 5–6 节：工作区与会话**

- [ ] **仅使用该能力时适用，插件负责**：业务流程需要创建文件或工作区时，从业务入口触发宿主目录选择器；取消后核对没有新目录、空项目或绑定，确认后核对文件只在所选位置。原目录丢失时验证重新选择和失败恢复。
- [ ] **仅使用该能力时适用，插件负责**：业务操作要给 Agent 发送或写会话草稿时，先在无会话状态打开面板，确认非会话功能可用；发起依赖会话的操作时创建/恢复本工作台会话，草稿由用户发送，切换已有会话后恢复其业务选择。
- [ ] **宿主负责，插件配合核验**：在同一工作区分别创建原生会话和两个工作台会话；从侧栏逐个打开，核对各会话只属于一个 owner，项目资料不因模式切换被搬移。

**第 7 节：模式切换与保留**

- [ ] **宿主负责**：用侧栏顶部模式切换器在“原生会话”和各工作台间切换，核对只显示对应会话，工作台图标、管理入口、焦点和折叠状态正确；切换工作区、新建会话后再核对归属。异常时记录 Desktop 版本和复现步骤，不能由插件私自改写宿主状态。
- [ ] **宿主负责，插件配合核验**：关闭重开、卸载后检查已有会话、项目文件、草稿、笔记和收藏仍在原位置；安装/卸载动作与数据保留分别记录。
- [ ] **插件负责**：记录 Desktop 版本、操作系统、架构、包版本、执行的 UI 路径及未验证项；无法安装时按第 9 节交付，不能勾选安装后的项目。

## 9. 失败时如何交付

最终必须列出真实结果和证据：修改的文件、包版本、本机安装与自测结果、未验证的项目。缺哪一项就写哪一项，不虚构成功。缺少工具链、安装失败或宿主接口不可用时，保留已完成的代码，说明原因和下一步。

## 参考

- DSH 插件打包与发布：deepseek-harness 仓库 `docs/user/develop/basic/publish.md`、`config.md`；包清单类型 `@deepseek-ai/dsh-package-manifest`；客户端模块加载 `@deepseek-ai/dsh-client-modules`。
- 上架到工作台市场：《工作台市场验收规范》：https://dshdesktop.com/workbench/docs/market-acceptance/ 。
