# dsh-custom-plugin

[![npm version](https://img.shields.io/npm/v/%40alexpeng%2Fdsh-custom-plugin?style=flat-square)](https://www.npmjs.com/package/@alexpeng/dsh-custom-plugin)
[![license](https://img.shields.io/badge/license-Apache--2.0-blue?style=flat-square)](LICENSE)
[![Node](https://img.shields.io/badge/node-%3E%3D22-339933?style=flat-square)](#安装)
[![TypeScript](https://img.shields.io/badge/TypeScript-5.7-3178C6?style=flat-square&logo=typescript&logoColor=white)](https://www.typescriptlang.org)
[![CI](https://github.com/AlexPeng07/dsh-custom-plugin/actions/workflows/ci.yml/badge.svg)](https://github.com/AlexPeng07/dsh-custom-plugin/actions/workflows/ci.yml)
[![Awesome DSH Plugin](https://awesome-dsh-plugin.com/badge.svg)](https://awesome-dsh-plugin.com)

<p align="center">
  <img src="https://www.npm.bet/svg?q=%40alexpeng%2Fdsh-custom-plugin&amp;timeRange=last-month&amp;grouping=day&amp;metric=downloads&amp;zeroMode=estimated" alt="npm 下载趋势（近一个月）" width="560">
</p>

[English](README.md) | 中文

DeepSeek Harness（DSH）Web GUI 的 Custom 便利套件：个性化外观、天气特效、玻璃效果、项目文件夹、增强提示词库、会话导出与会话搜索、Mermaid 渲染、引用回复、7/30/90 天用量分析、预算与无密钥本地备份，以及 Ctrl/Cmd+K 快捷面板。

插件为双半区架构：宿主半区（`src/`）持有状态文档、注册 `/api/custom-plugin` 路由与 `custom_plugin_status` 智能体工具；浏览器半区（`src/client/`）通过 7 个官方 slot 的 8 处注入挂载 UI，以同源 fetch 与宿主通信。经官方 profile 机制挂载，不改 DSH 源码。

<p align="center">
  <img src="https://github.com/AlexPeng07/dsh-custom-plugin/raw/main/docs/overview.png" alt="dsh-custom-plugin 总览" width="100%">
</p>

## 部分界面展示

<table>
<tr>
<td width="50%" valign="top"><b>「设置 → 个性化」整页外观配置</b><br><img src="https://github.com/AlexPeng07/dsh-custom-plugin/raw/main/docs/settings-appearance.png" alt="设置 → 个性化 整页外观配置" width="100%"></td>
<td width="50%" valign="top"><b>会话头部弹出的同一套面板（浅色主题）</b><br><img src="https://github.com/AlexPeng07/dsh-custom-plugin/raw/main/docs/personalization-panel.png" alt="个性化弹窗面板" width="100%"></td>
</tr>
<tr>
<td width="50%" valign="top"><b>液态玻璃（Custom 面板位移折射）</b><br><img src="https://github.com/AlexPeng07/dsh-custom-plugin/raw/main/docs/liquid-glass.png" alt="液态玻璃" width="100%"></td>
<td width="50%" valign="top"><b>多级项目文件夹</b><br><img src="https://github.com/AlexPeng07/dsh-custom-plugin/raw/main/docs/project-folders.png" alt="项目文件夹" width="100%"></td>
</tr>
<tr>
<td width="50%" valign="top"><b>Mermaid 思维导图就地渲染</b><br><img src="https://github.com/AlexPeng07/dsh-custom-plugin/raw/main/docs/mermaid-mindmap.png" alt="Mermaid 思维导图" width="100%"></td>
<td width="50%" valign="top" align="center"><b>余额与今日分模型用量</b><br><img src="https://github.com/AlexPeng07/dsh-custom-plugin/raw/main/docs/balance-usage.png" alt="余额与用量面板" width="100%"></td>
</tr>
<tr>
<td width="50%" valign="top" align="center"><b>雨（三层景深）</b><br><img src="https://github.com/AlexPeng07/dsh-custom-plugin/raw/main/docs/weather-rain.gif" alt="下雨特效" width="100%"></td>
<td width="50%" valign="top" align="center"><b>樱花</b><br><img src="https://github.com/AlexPeng07/dsh-custom-plugin/raw/main/docs/weather-sakura.gif" alt="樱花特效" width="100%"></td>
</tr>
<tr>
<td width="50%" valign="top" align="center"><b>飘雪</b><br><img src="https://github.com/AlexPeng07/dsh-custom-plugin/raw/main/docs/weather-snow.gif" alt="飘雪特效" width="100%"></td>
</tr>
</table>

天气特效截自深色模式。额度历史、备份导入预览、会话搜索与快捷面板属于同一套动态面板，需在 DSH 内打开对应页查看；仓库未另附固定截图。

## 功能

### 外观个性化

- **背景颜色**：20 组低饱和典雅色（每组合配 tab 栏色，默认「天青灰」），另有「无颜色」（跟随 GUI 默认主题）。深浅色模式均可选用；深色模式自动派生同色系的深色变体（保留色相与低饱和，落到深色明度档），色板预览即当前模式的实际效果。
- **天气特效**：画布渲染、不挡交互，一键切换——飘雪、电影感雨滴（三层景深 + 地面溅起）、樱花飘落；关闭时自动清空画布。
- **玻璃效果**：所有 Custom 面板默认毛玻璃；可切换液态玻璃（边缘位移折射 + 轻微模糊提饱和，大面积面板中部文字仍清晰；不支持 SVG 位移滤镜的引擎自动回退毛玻璃）。「全局浮层玻璃」对弹窗、菜单、提示框、下拉框与系统设置窗统一加玻璃质感，液态模式下浮层同样位移折射。

### 项目文件夹

多级文件夹树，保存在 `$DSH_HOME` 状态文件中，跨工作区共享。任意工作区与会话都可收纳进文件夹；支持拖拽排序（插入前 / 内部 / 插入后）、重命名、删除与「添加当前会话」。

### 提示词库

提示词支持新增、编辑、复制、删除、搜索、收藏、标签和拖拽排序，并记录最近使用与使用次数。正文可使用 `{{变量名}}`；插入前会弹出变量表单。提示词库可用版本化 JSON 或以二级标题分段的 Markdown 导入导出。

### 会话导出

导出当前会话为三种格式（文件名带日期戳）：

- **JSON**：标准 `messages` 结构（user / assistant / tool），meta 携带会话标题、创建时间、工作目录与导出时间，可导入其他工具；
- **Markdown**：按角色分块的纯文本；
- **PDF**：A4 打印版式 HTML，浏览器打开后打印另存为 PDF；图片以 base64 内嵌（最多 30 张、总量 12 MB、单张 ≤ 4 MB）。

工具调用行携带工具名与参数摘要（从配对的 tool/call 事件解析）。

插件状态可导出为版本化 JSON 备份，包含外观、文件夹、提示词、星标、用量和预算，不包含 API Key 或凭据元数据。导入前显示冲突预览，支持合并或覆盖非敏感状态，并自动保留恢复副本。

### Mermaid 渲染

聊天中的 ```mermaid 代码块（含助手回复，思维导图 / 流程图 / 时序图等）自动就地渲染为图表：代码块上方出现「图表 / 代码」切换工具条与 mermaid.live 兜底链接，流式输出期间内容完整后即预览，跟随 GUI 明暗主题重绘，渲染失败时保留原代码。检测依据代码块语言标签；无标签（流式中）时按内容启发式判断（关键字前缀 + 完整度检查，语言明确的代码块不会误触）。引擎优先读取随插件安装的本地 Mermaid 11 依赖（离线可用），缺失时回退 jsdelivr / fastly / unpkg 三镜像拉取并在宿主进程内缓存；用户消息下方的渲染按钮与模态窗口（多图切换）保持不变，mermaid.live 链接为 DEFLATE 压缩的 `#pako:` 格式，打开即还原图表。

### 效率工具

- **引用回复**：选中对话文本后出现「引用回复」按钮，以引用块插入输入框；
- **防自动跳转**：强制 `scroll-behavior: auto`，发送消息不再把视图拽到底部（默认关闭）；
- **公式复制**：含公式的消息下方显示 LaTeX / MathML 复制按钮（MathML 可直接粘贴进 Word）；
- **批量归档**：勾选多个会话批量归档，运行中会话默认不可选，并分别报告成功与失败数量；DSH 尚未公开恢复接口，插件不提供恢复入口。
- **会话搜索**：搜索当前会话的用户、助手和工具内容，点击结果跳转到所属轮次；部署开启了 dsh 事件索引时走索引，否则直接扫描同一份会话日志，面板会说明本次用的是哪条路径。
- **快捷面板**：在非编辑状态按 `Ctrl+K` / `Cmd+K`，统一搜索会话、工作区、提示词和常用功能；跨会话全文搜索使用 dsh 官方接口，不可用时如实提示原因。
- **可靠归档**：运行中的会话默认不可选，批量操作分别报告成功与失败数量。

### 额度与用量

会话头部常驻用量徽标（可点击固定）：配置了 API Key 时显示官方余额，未配置时显示本机费用估算（如 `≈¥0.42`）与今日调用次数。面板提供：

- **用量与费用（默认区，无 Key 可用）**：今日/历史用量、预算与费用估算全部来自本机会话记录，不需要任何密钥。
- **余额查询（可折叠可选项，需要 API Key）**：调用官方 `https://api.deepseek.com/user/balance` 接口，优先显示 CNY，赠送与充值余额分列，并显示账户可用状态；未配置 Key 时该区收起、不显示任何报错。宿主外呼走本机直连（不读系统代理）；若本机代理/安全软件间歇性对 HTTPS 做 TLS 解密，查询会以「TLS 证书校验失败」等可读原因失败并自动重试（8s/30s/30s，60 秒轮询兜底），顶栏悬停可见原因。
- **Key 解析顺序**：系统凭据存储（可用时）→ 插件旧状态文件中的 Key → 环境变量 `DEEPSEEK_API_KEY` / `DEEPSEEK_KEY` / `DEEPSEEK_TOKEN`（取值需以 `sk-` 开头）→ DSH 凭据文件 `$DSH_HOME/.credentials.yaml`（自动复用 DSH 已配置的 DeepSeek key，无需重复填写）。桌面版账号登录没有 sk- 密钥，属正常状态。
- **今日用量**：按模型统计输入 / 输出 / 缓存 token 与调用次数，实时折叠自 `session/event` 事件。
- **费用估算**：按 DeepSeek 当前官方峰谷价目估算（2026-09-30 核对）——高峰为北京时间周一至周五 9–12、14–18 时；其余时间（含周末）均为空闲时段，按半价计：`deepseek-flash`（旧名 `deepseek-v4-flash` / `deepseek-v4-flash-vision-exp` 仍按 Flash 计价路由）峰值 ¥2 / ¥8，`deepseek-v4-pro` ¥9 / ¥27（每百万 tokens 峰时输入 / 输出，缓存命中 ¥0.04 / 写入与未命中输入同价档），仅供参考。
- **扫描**：「扫描今日会话日志」重放全部会话、按事件自身时间戳归入今日（跨午夜会话不丢量），完成后显示扫描到的活跃会话数。
- **历史与预算**：查看 7 / 30 / 90 天趋势和按模型汇总，导出 UTF-8 CSV；可设置人民币月预算与预警比例。

### 设置入口

「设置 > 个性化」页提供外观与工具开关的整页配置；会话头部与侧边栏底部的「个性化」按钮打开同一套面板弹框。

### 智能体集成

`custom_plugin_status` 工具报告：外观配置、今日按模型用量、余额、时间线样本、Mermaid 引擎加载情况、状态文件路径与客户端诊断。插件不注入任何系统提示。

## 版本对应

| 插件版本 | 构建并实测过的 dsh | 状态 |
| --- | --- | --- |
| 0.9.x | 任何 >= 0.1.7-rc.2 的 dsh（开放式；已在 0.1.7-rc.2、0.2.0-rc.2、0.2.1-rc.2 与 0.2.1-rc.3 实测） | 本版本 |
| 0.8.x | dsh 0.1.7-rc.2、dsh 0.2.0-rc.2 与 dsh 0.2.1-rc.2（实测窗口） | 上一版；在 dsh 0.2.1-rc.3 及以后会被拒绝 |
| 0.7.x | dsh 0.1.7-rc.2 与 dsh 0.2.0-rc.2 | 上一版；在 dsh 0.2.1-rc.2 上会被拒绝 |
| 0.6.x | dsh 0.1.7-rc.2 与 dsh 0.2.0-rc.2 | 上一版；在 dsh 0.2.1-rc.2 上会被拒绝 |
| 0.5.x | dsh 0.1.7-rc.2（peer 范围 `>=0.1.7-rc.2 <0.2.0-0`） | 在 0.1.x 上没问题；任何 0.2.x 宿主都会拒绝它，dsh 桌面版也算 |
| 0.4.2 | dsh 0.1.1-rc.1 … 0.1.6 时代 | 已被取代，在 0.1.7+ 上不要指望可用 |

0.1.7 改掉了本套件依赖的插件侧契约（会话导航归 `ctx.uiWorkspace`、会话列表不再提供
当前查看的会话、消息模型移除 `tool-result` 内容块），两个版本互不替代。dsh
0.2.1-alpha.4 又改掉了兼容门本身：优先读 `engines.dsh`，只有它缺省时才回退到
`@deepseek-ai/dsh-*` peer 范围。本包两处声明同一条**开放式下限**（`>=0.1.7-rc.2`），
因此任何更新的 dsh 版本都会被放行，不必等插件发版；旧宿主读 peer，新宿主读
`engines.dsh`，`pnpm smoke` 会在两者判定不一致时判红。实测过的版本是证据、不是闸门，
见上表与 CHANGELOG。定时 canary 会把已发布插件装进 npm `latest`/`next` 里最新的
dsh，真实启动并跑活体探针——真的 API 断裂会变成 issue，而不是用户眼前的“不兼容”。
没有这道闸的更早宿主仍会加载插件，但表现为功能退化：实测
0.1.1-rc.2 上 8 个槽位注册成 7 个，turnTail 条目被拒（该槽位自 0.1.6-alpha.2 起由
chain 改为 list），打开会话、创建分支、跨会话搜索会提示对应服务缺失，而不是抛错。

## 安装

前置：Node 22+、pnpm，以及 `dsh` CLI（官方 npm 包 `@deepseek-ai/dsh`；未全局安装时，可用 `npx @deepseek-ai/dsh` 代替 `dsh`）。本版本放行**所有 >= 0.1.7-rc.2 的 dsh**：`engines.dsh` 与 `@deepseek-ai/dsh-tools` peer 都是开放式下限，所以 dsh 0.2.1-alpha.4 及更新版本（优先读 `engines.dsh`）与旧宿主（读 peer）都不会拒绝更新的 dsh。0.9.0 的构建与测试队列是 0.2.1-rc.3。产物用桌面版自带 CLI 装进隔离的 0.2.1-rc.3 profile，**没有使用任何兼容豁免**，随后真实冷启动：boot 图 71 行、五个 `dsh.client.inject` 目标齐全、客户端 bundle 200,371 字节、Mermaid 本地引擎 3,572,661 字节、state / 用量 / 备份路由、UTF-8 往返与桌面转发信任形状全部通过（16 项通过、1 项跳过；跳过的是无会话 profile 上的时间线/导出/搜索，该路径由单测覆盖）。0.8.0 的产物在 0.2.1-rc.2 上跑过同一套探针（70 行）；0.6.0 的产物在 0.2.0-rc.2（66 行）与 0.1.7-rc.2（65 行）上各跑过 19 项里的 18 项；0.7.0 的产物额外在**真实桌面安装**上过了桌面形态活体探针（见下文「在 dsh 桌面版上」）。验不到的部分和以往一样：回合内的 chips（Mermaid 渲染、LaTeX）需要一次真实的模型回复，无凭据的临时家目录跑不出来，槽位渲染也仍要在浏览器里过一遍。宿主的兼容门分不清“实测过”与“没实测过”，所以写死上界会让每个新 dsh 版本都等到插件再次发版才可用；开放式下限加定时 canary 把这段等待换成自动发现。若确实要跑低于下限的宿主，请安装对应的旧版插件（0.5.0 面向 0.1.7-rc.2）。

### 从 npm 安装

```sh
dsh plugin --profile web add @alexpeng/dsh-custom-plugin
# 重启 dsh web
```

registry 上是预构建产物，安装端无需从源码构建。

### 在 dsh 桌面版上

dsh 桌面版是同一套 Web 应用外面的一层 Electron 壳，截至 2026-10-23 随包携带
**正好是 dsh 0.2.1-rc.3**——桌面发布线把壳和运行时钉成同一个版本，所以桌面端拿到
的永远是某一个确定的 0.2.x 运行时。0.9.0 的开放式范围收 rc.3 及之后的每个桌面构建，
安装不需要任何兼容性豁免；0.8.0 及更早用的是实测窗口，在 rc.3 上会被同一道门拒绝，
给出的信息和 0.2.x 的 Web profile 完全一样。

对 0.2.1-rc.3 的实测：boot 图里 71 行、我们那一行与五个 inject 目标都在、客户端
bundle 以 200,371 字节服务、Mermaid 引擎本地加载，活体宿主探针 16 项通过、1 项跳过
（跳过的是需要真实会话的会话路径）。访问围栏与官方 API 围栏同一套信任语义：回环
套接字 + 回环 Host、非 `cross-site`、`origin` 缺省或与 Host 一致即放行。壳在把
渲染进程的请求转发给它自己的 Host 之前会删掉 `host`、`origin`、`sec-fetch-site`、
`cookie`，再挂上它自己的凭证——这种无标记形状因此被直接接受（0.6.0 及之前的围栏
还额外要求同源标记，曾把这套形状全部误拒为 forbidden，0.7.0 起修复并补了围栏级
单测）。异源与跨站请求仍然 403。**0.7.0 的产物已在真实桌面安装上按桌面转发形状
逐路由实测过**（state / debug / backup / 用量扫描 / Mermaid 引擎预加载与脚本路由
全部 200，异源与跨站仍 403）；0.8.0 在冷启动的 0.2.1-rc.2 宿主上复跑了同一套形状，
0.9.0 在 rc.3 上再跑一遍。
桌面窗口内的视觉验收（液态玻璃观感、面板布局、点阵清除）归装它的人过目。

管桌面 profile 要用桌面版自带的 `dsh` 命令，不是 npm 装的那个——公开 CLI 拒绝
保留的 `desktop` profile：

1. 先启动一次桌面版让 profile 建立，然后完全退出（关窗口只是隐藏；Windows 上
   从托盘退出）。
2. `dsh plugin --profile desktop add @alexpeng/dsh-custom-plugin`
3. 重新打开桌面版。应用内的插件管理器也用桌面版自带的 pnpm 在同一个 profile 上
   安装和更新，不想开终端就用它。

外观、提示词库、项目文件夹、星标和用量都存在
`$DSH_HOME/custom-plugin-state.json`，这个文件按设计由桌面版和 Web 共享——两
边各自持有自己的 `profiles/<名字>`，那里面只放代码不放这个文件——所以在两者
之间切换看到的是同一份数据。两个宿主因此可能同时写它，这就是状态替换把临时文
件按写入进程的 pid 命名的原因。

### 从 GitHub 安装（源码安装）

```sh
dsh plugin --profile web add github:AlexPeng07/dsh-custom-plugin
# 重启 dsh web
```

git 安装拉取的是源码，`prepare` 脚本会在安装端构建 `lib/`。pnpm ≥10 需要一次性授权该构建——把 pnpm 提示的确切包键复制进 profile 的 `pnpm-workspace.yaml` 的 `allowBuilds` 下，再重新执行 add。走上方 npm 路线可免去构建授权。

### 本地链接（开发）

```sh
# 构建（本仓库根目录执行）
pnpm install
pnpm build
# 装入 web profile。链接路径不能含空格：Windows 上若源码路径含空格，
# 先创建无空格目录联接（如 F:\dsh-plugin-dev），再链接到联接路径
dsh plugin --profile web add link:F:/dsh-plugin-dev
# 重启 dsh web
```

包按官方组合包协议声明 manifest：`package.json` 的 `dsh.bundle.patch` 指向 `cordis.patch.yml` 配置层（行 id `custom-plugin`），`dsh.client` 声明浏览器半区。`dsh plugin add` 在 profile 目录内转发给 pnpm，安装后因该声明被自动加入 `dsh.profile.bundles`；浏览器半区经官方客户端模块系统按同一行加载。

## 配置

插件读写 `$DSH_HOME/custom-plugin-state.json` 单个 JSON 文档（默认 `~/.dsh`，可用 `DSH_HOME` 环境变量覆盖），包含外观配置、文件夹、提示词、星标、兼容旧版的数据与最近 90 个北京时间日的用量账本；写入为原子写（临时文件 + 重命名），崩溃不会截断文档。

外观与功能开关（`cfg` 字段，均有默认值）：

| 配置项 | 默认值 | 说明 |
|---|---|---|
| `bg` | `天青灰` | `default`（无颜色）/ 20 组调色板名之一（深浅色通用；旧存档的 `aurora` 自动迁移为 `default`） |
| `weather` | `none` | `none` / `snow` / `rain` / `sakura` |
| `glass` | `true` | Custom 面板玻璃总开关 |
| `glassMode` | `frost` | `frost` 毛玻璃 / `liquid` 液态玻璃 |
| `globalGlass` | `true` | 全局浮层（弹窗/菜单/提示）加模糊 |
| `quote` | `true` | 划词引用回复 |
| `antiScroll` | `false` | 防自动跳底 |
| `mermaid` | `true` | Mermaid 图表自动就地渲染（含消息下方渲染按钮） |
| `formula` | `true` | LaTeX / MathML 复制按钮 |
| `monthlyBudgetCny` | `0` | 人民币月预算；`0` 表示关闭提醒 |
| `budgetWarningPercent` | `80` | 月预算预警百分比（1–100） |

## 安全模型

- 浏览器仅通过回环地址上的 `/api/custom-plugin` 路由与宿主通信；每条路由同时校验回环 socket 地址、回环 Host 头与浏览器同源标记（`sec-fetch-site` / `Origin`），`X-Forwarded-For` 永不信任。
- 浏览器永远不会收到已保存的 DeepSeek API Key。面板新输入的 Key 在 `keytar` 存在时优先写入系统凭据存储；旧版状态文件中的明文 Key 会在系统存储可用时启动迁移。`keytar` 不再作为本插件的依赖发布（原生模块会触发 pnpm 11 的严格构建门禁，导致整个插件包安装后无法激活）；需要系统钥匙串的用户可自行加入 profile：`dsh plugin --profile web add keytar`。
- 如果系统凭据存储不可用，插件会兼容回退到 `$DSH_HOME/custom-plugin-state.json`；请相应保护 `$DSH_HOME` 目录。DSH 自身的 `$DSH_HOME/.credentials.yaml` 明文凭据仍可复用。
- 会话导出与时间线数据全部停留在本机。

## 已知限制

- Mermaid 引擎来自随插件安装的本地依赖，离线可用；仅当依赖缺失时回退 CDN 拉取（宿主在进程生命周期内缓存）。
- 用量账本折叠自实时 `session/event` 记录，只保留最近 90 个北京时间日；错过实时事件时可手动「扫描」，扫描最多并发读取 4 个会话日志。
- 额度面板会显示峰/闲 token 与费用分布，并提供官方价目链接；没有峰时字段的历史行会标记为“不精确”，不计入费用总额，重新扫描后可更新。
- 系统凭据存储会在宿主启动时检测 profile `node_modules` 中的 `keytar` 模块；无法加载时使用兼容的状态文件回退。
- 费用按 DeepSeek 官方峰谷单价估算，仅供参考。
- 色板在深色模式自动派生同色系深色变体（保留色相与低饱和）。
- dsh 0.1.7 起支持自定义快捷键。快捷面板固定占用 `Ctrl/Cmd+K`（不带 Alt/Shift，且编辑器内不触发）。dsh 在 **web** 上的默认值是 `Ctrl+Alt+K`（桌面版默认才是裸 `Ctrl+K`），所以开箱并不冲突；但你可以把某个 dsh 命令重新绑到 `Ctrl+K`，那样两者会同时响应，请把其中一个换开。
- DSH 当前没有公开的归档恢复接口；插件不会绕过官方边界修改底层注册表。
- dsh 自带的 `web` profile 把 session-query 索引配成 `openAt: never`，即默认不启用全文搜索。此时会话内搜索改为直接扫描日志（按出现顺序，最多 100 条），快捷面板的跨会话搜索会显示 dsh 给出的不可用原因。

## 开发

```sh
pnpm typecheck      # 类型检查
pnpm test           # vitest 单元测试
pnpm build          # 构建 node ESM 库与浏览器 bundle 到 lib/
pnpm check:readme   # 双语 README 哈希一致性
pnpm smoke          # 构建后契约自检：加载器握手、dsh 会读的清单字段、
                    # patch 行、本地 Mermaid 引擎与真实引擎的失败路径 DOM 回归
pnpm canary         # 已发布插件对 npm latest/next 里最新的 dsh（需要 npm、bash、
                    # curl；--plugin <tgz> 可测本地构建）
pnpm bench:mermaid  # 合成长回合的 Mermaid 扫描/渲染基准（无头 Edge）
```

`scripts/live-dsh-check.sh` 是另一条**手动**探针，对着一个正在运行的隔离 dsh profile
（`DSH_HOME=… DSH_PORT=… bash scripts/live-dsh-check.sh`），在真实会话数据上跑宿主侧全链路：
时间线、三种导出、搜索扫描路径、用量扫描、备份、Mermaid 引擎路由、客户端→宿主的诊断回环、
一次会还原并校验提示词库的 UTF-8 往返，以及访问围栏的三种形态（无标记回环放行、异源拒绝、跨站拒绝）。默认拒绝在真实的
`~/.dsh` 上运行（需显式 `ALLOW_REAL_DSH_HOME=1`），逐项报 PASS/FAIL，被 `SKIP` 的项会计数并写进收尾行，任一失败即非零退出。
主 CI 没有可对话的宿主；定时的 `compat-canary` workflow 会对 npm `latest`/`next` 里最新的 dsh 自动跑这套探针，失败时开 issue。想看某一版的细节时再手动跑；槽位注册与渲染仍需按上文用浏览器过一遍。

`scripts/desktop-shape-probe.mjs` 是第三条探针，面向**正在运行的 dsh 桌面版**。
桌面版 0.2.1+ 让系统分配监听端口，所以用 `DSH_PORT` 传入它打印的（或正在监听的）
端口——旧版本默认是 19387。探针以桌面壳转发的请求形状（回环 Host、无 `origin` /
`sec-fetch-site`）逐路由探测 state / debug / backup / 用量扫描 / Mermaid 引擎与
脚本路由，发现宿主已注册的会话后补跑时间线、三种导出与搜索；异源与跨站探针必须
仍是 403。用量扫描会重写当日台账，介意时把 `DSH_HOME` 指向临时家目录，其余探针
只读；GUI 里还没有会话时先发一条消息再重跑即可。

自己验一个新 dsh 版本只要四条命令，且全程不碰你自己的 harness 家目录：

```bash
mkdir -p /tmp/dshnext && cd /tmp/dshnext && npm init -y && npm i @deepseek-ai/dsh@<版本>
DSH_HOME=/tmp/dshnext-home node node_modules/@deepseek-ai/dsh/lib/bin.js plugin --profile web add <打包好的.tgz>
DSH_HOME=/tmp/dshnext-home node node_modules/@deepseek-ai/dsh/lib/bin.js --profile web --no-open --port 13999 &
DSH_HOME=/tmp/dshnext-home DSH_PORT=13999 COOKIE_JAR=/tmp/dshnext/jar.txt bash scripts/live-dsh-check.sh
```

范围是开放式下限，所以第二条命令会接受任何 >= `0.1.7-rc.2` 的版本，不需要兼容豁免。
定时 canary 对 npm `latest`/`next` 里最新的版本自动执行这四步；发版前想测本地构建，
可跑 `node scripts/compat-canary.mjs <版本> --plugin <打包好的.tgz>`。

验**桌面版**发版要往前多走一步，因为仓库回答不了"用户装的那一版里到底是哪个 dsh"：
桌面版把壳和 `@deepseek-ai/dsh` 钉成同一个精确版本，整套运行时打在
`resources/app.asar` 里。直接读装好的那个 app，命令只读不写，它会把内置版本连同
"本仓库声明的 `engines.dsh` / peer 范围收不收它（以及 dsh 实际会读哪道门）"一起打出来：

```bash
node scripts/desktop-runtime.mjs "D:/DSH"                    # 传安装目录
node scripts/desktop-runtime.mjs "D:/DSH/resources/app.asar" # 或直接传归档文件
```

拿到版本号后，再按上面四条命令对着它跑一遍。

## 许可证

Apache-2.0。部分代码参考 [Nagi-ovo/voyager](https://github.com/Nagi-ovo/voyager) 与 [unovue/inspira-ui](https://github.com/unovue/inspira-ui)。
