每次启动 DeepSeek Harness 时随机启用一个已安装皮肤,并在「设置 → 皮肤」页内提供开关与「立即随机换一个」按钮。宿主会拒绝加载的皮肤自动排除;手动切换的皮肤一直有效到下次启动。
安装
# npm 包(预构建)
dsh plugin --profile web add dsh-skin-rotation
# Release 预构建包
dsh plugin --profile web add "https://github.com/luoyuejun9/dsh-skin-rotation/releases/latest/download/dsh-skin-rotation.tgz"
# GitHub 源码(首次需按提示配置 allowBuilds 构建授权后重试)
dsh plugin --profile web add github:luoyuejun9/dsh-skin-rotation
装任何插件都等于在你的机器上跑第三方代码,权限和你本人一样大——能读你的文件、用你的凭据、访问网络,工具审批管不到它。GitHub 来源的插件还会在安装时执行构建脚本——pnpm 默认拦截,所以安装可能停在 ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED 或 ERR_PNPM_IGNORED_BUILDS;dsh 会打印出需要添加的确切键名,把它加进该 profile 的 pnpm-workspace.yaml 的 allowBuilds 下,重跑一次即可装上。放行构建本身就是一次信任判断:请只安装可信来源,并尽量锁定 commit(github:owner/repo#sha)。
README
它做什么
你装了四个皮肤,四个都喜欢,但每次只用其中一个。
dsh-skin-rotation 会在每次启动 DeepSeek Harness 时按你选的轮换策略启用一个皮肤,控制项都在「设置 → 皮肤」页里:
| 策略 | 行为 |
|---|---|
random 随机 |
每次启动换一个,可选避开最近 2 / 3 次用过的 |
sequential 顺序 |
按你的顺序依次轮换,重启后从正确位置继续 |
weighted 加权 |
按权重比例抽取(权重不必凑成 100) |
fixed 固定 |
始终使用指定皮肤,不做自动切换 |
它同时是可解释、可恢复的:哪些皮肤在候选池里、其余为什么被排除、什么时候换过什么、上一次成功的皮肤是哪一只(一键恢复)。
第 1 次启动 → 深海女仆工坊 第 2 次启动 → 虎鲸链路 第 3 次启动 → 鲸鱼娘昼夜工坊
不只是"随便抽一个"
- 真正的候选池:每个已安装皮肤都会被标为
compatible(参与)/excluded(你排除的)/incompatible(宿主不兼容)/unavailable(读不到)。排除 ≠ 卸载,插件绝不碰你已安装的文件。 - 只抽宿主真能加载的皮肤:DSH 会拒绝
@deepseek-ai/dsh*peer 范围不含当前运行时的插件行(补丁写着启用、但行根本不启动),naive 的随机可能抽到"死皮肤"导致一个皮肤都没有。本插件复刻了这道准入判定,并读取 profile 的compatibility.json放行记录。 - 可读的轮换历史:时间、从哪只到哪只、触发原因(
startup/manual/restore)、策略、结果。失败会如实记为失败,且不会进入"最近使用"集合。 - 恢复上一皮肤:一键回到最近一次成功、且当前仍在候选池里的皮肤。
- 崩溃安全的配置写入:轮换要改两层补丁文件,事务日志保证被中断的写入在下次启动时要么补完、要么回滚;对于"变了但不是它改的"文件,它拒绝覆盖并给出诊断。
- 带版本的状态文件:
schemaVersion+ 向前迁移,升级不丢偏好。
安装
从 GitHub 一行装:
dsh plugin --profile web add github:luoyuejun9/dsh-skin-rotation
从 npm 装:
dsh plugin --profile web add dsh-skin-rotation
或者用 最新 Release 上的预构建 tarball——不需要构建,也不会触发 allowBuilds 授权:
dsh plugin --profile web add https://github.com/luoyuejun9/dsh-skin-rotation/releases/latest/download/dsh-skin-rotation.tgz
重启一次,然后打开「设置 → 皮肤」,轮换卡片就在这个页面底部。
装进桌面端而不是 web profile?把
--profile web换成--profile desktop即可,两边都能用。
设置卡片
设置 → 皮肤
├── 已安装皮肤 ← 皮肤管理器自己的页面
└── 🎲 皮肤轮换 ← 本插件
├── [开关] 启动时自动轮换 轮换模式:[随机 ▾] 近期不重复:[避开最近 2 个 ▾]
├── 当前皮肤:虎鲸链路 · 候选池 2 个
├── ☑ 虎鲸链路 · 权重 [3]
├── ☐ 深海女仆工坊 (已排除)
├── ☐ 不兼容皮肤 (不兼容,已排除)
├── [立即换一个] [恢复上一皮肤] [重新读取]
├── 最近轮换
│ 2026-10-11 21:04 · 启动 · 官方默认 → 虎鲸链路 · 成功
└── ▸ 高级设置 (权重说明、恢复默认配置)
| 控件 | 作用 |
|---|---|
| 启动时自动轮换 | 关闭后启动不改动皮肤;「立即换一个」仍然可用。 |
| 轮换模式 | random / sequential / weighted / fixed。fixed 下你指定皮肤,不再自动切换。 |
| 近期不重复 | 优先避开最近 2 / 3 次成功轮换用过的皮肤。这只是偏好、不是阻塞:当候选池里全是"最近用过"的,它照样会选一个并如实说明。 |
| 皮肤勾选框 | 是否参与轮换。取消勾选 = 排除,不是卸载;不兼容的皮肤不可被勾选。 |
| 权重 | 仅 weighted 模式显示;填 0 表示不参与抽取,留空表示默认权重 1。 |
| 恢复上一皮肤 | 回到最近一次成功且仍在候选池中的皮肤,并在历史里记为一次 restore。 |
在皮肤管理器里手动切换皮肤依然有效——直到下次启动,这正是这个插件的意义。
工作原理
flowchart LR
A[宿主启动] --> B{轮换开启?}
B -- 否 --> Z[保持当前皮肤]
B -- 是 --> C[发现已安装皮肤]
C --> D[排除宿主会拒绝的皮肤]
D --> E[随机抽一个,且不与上次相同]
E --> F[改写 profile patch + home patch 里的<br/>dsh-skin managed 区段]
F --> G[DSH 的 HMR 监听器重新组合插件树]
G --> H[页面打开时已经是抽中的皮肤]
- 轮换是宿主侧的决定,一次启动只做一次。 进程级 boot key 保证插件被热重载时不会重复抽签,真正重启才会重抽;浏览器刷新不会重掷骰子。
- 它改的是补丁层,不是皮肤。 插件改写的是皮肤管理器自己也在用的那段
# --- dsh-skin managed ---互斥区段,profile patch 与优先级更高的 home patch 一起改。区段之外的一切——手写行、注释、DSH 自己追加的设置行——原样保留,写入走临时文件 + rename 的原子替换。 - 只抽宿主真能加载的皮肤。 DSH 会拒绝那些
@deepseek-ai/dsh*peer 范围不含当前运行时的插件行:补丁里写着disabled: false,行却根本不会启动,结果是一个皮肤都没有。本插件用自己的语义化版本求值器复刻了这道准入判定,并读取 profile 的compatibility.json放行记录,因此随机结果不会落到死皮肤上。 - 浏览器半区只有一张卡片。 它监听 DOM,往皮肤管理器页面根节点追加一个容器,把卡片渲染进去;不注册自己的设置页、不留全局状态,卸载时清掉自己创建的 observer、定时器和样式。
- 不碰皮肤文件、
skin.json,也不接管管理器自己的状态。
写入的文件
| 路径 | 内容 |
|---|---|
<profile>/cordis.patch.yml |
共用 dsh-skin managed 互斥区段 |
<DSH_HOME>/cordis.patch.yml |
同一区段——优先级更高,必须一起改 |
<profile>/.dsh-skin-rotation.json |
本插件自己的偏好:开关、上次抽中的皮肤、启动标识、客户端回报 |
HTTP 接口:GET/POST /api/dsh/skin-rotation,仅同源,跨站请求直接拒绝。
环境要求
- DeepSeek Harness web profile,
>=0.2.0-rc.2 <0.3.0-0(声明在engines.dsh)。 - 至少装两个皮肤轮换才有意义。皮肤的判定沿用生态约定:包内带合法
skin.json(id、package、bodyAttr、wiring.id)。 - 卡片挂在皮肤管理器的皮肤页(
settings.sectioniddsh-skins)里;没有管理器时轮换依然工作,只是改用路由开关。 - 不依赖任何
@deepseek-ai/*运行时包:无需解析、无需构建、安装时不执行任何脚本。 - Windows 与 POSIX 都适用:写入用临时文件 + rename,恢复日志是纯 JSON。
- 已被 dsh.so 独立收录:安全审计通过(低风险),安装验证
L5(已在 dsh0.2.0-rc.2上实机跑通)。
开发
node --test # 29 个用例
npm pack --pack-destination dist # 打包发布用的 tarball
| 测试 | 覆盖内容 |
|---|---|
test/host.test.mjs |
区段改写(含区段外记录保留)、真实 profile 的发现、随机规则、启动一次性、双层写入与回滚、路由防护 |
test/client-card.test.mjs |
整棵卡片渲染树,通过桩模块加载器 + 桩 React 驱动,不需要浏览器 |
test/semver-oracle.test.mjs |
与真实 node-semver 的差分验证,覆盖生成的组合矩阵(本机没装时自动跳过) |
tools/e2e-rotation.sh |
在独立 DSH_HOME 下真实启动宿主多次,断言抽中的皮肤就是浏览器实际拿到的那个 |
端到端脚本是里面最有意思的一个:它不验证插件「自认为」做了什么,而是去取浏览器实际收到的 boot graph,确认里面只有一个皮肤模块。
FAQ
会和皮肤管理器打架吗? 不会。两边用同一套规则写同一段区段;本插件只在启动时轮换,之后完全不插手。
浏览器刷新会重抽吗? 不会——每个宿主进程只抽一次,否则每次刷新都换皮肤。
只装了一个皮肤呢? 轮换等于不换,区段照样会被规范化。
能一直用我现在这个皮肤吗? 关掉开关即可,启动不再改动皮肤,手动切换也不受影响。
项目边界
本插件只管在你已安装的皮肤之间轮换:候选池、策略、历史记录与配置写入的可靠性。它刻意不做皮肤的安装、下载、卸载和预览,也不取代皮肤管理器或插件市场——那些已经有成熟项目,本插件与它们并存。
路线图
- 轮换方案(Presets)已支持保存与一键切换;可视化的方案编辑器暂不在计划内。
- 状态文件里保留了随机种子(seed)字段,用于可复现的运行;暂未在 UI 暴露。
- 会话级主题(不同会话用不同皮肤)未实现:DSH 当前没有公开、稳定的会话级主题切换接口,因此它留在路线图里,而不是用不可靠的 DOM 手段假装做到。
License
MIT © luoyuejun9
链接
同类插件
Small-tailqwq/dsh-deep-whale#maid-atelier★ 2643
DSH Web 鲸鱼娘皮肤系列(深海女仆工坊 maid-atelier)。
elysia395/dsh-wallpaper-engine★ 1422
前置要求:DeepSeek Harness 0.1.5-rc.1+(DSH Desktop ≥ 2.0.7)且 dsh-better-sidebar ≥ 0.19.0,安装或更新本插件前请先更新两者。把本机 Wallpaper Engine 的 Video/Web 壁纸实时播放到聊天界面后方,Scene 壁纸由内置渲染器输出完整场景帧,支持内容分级/类型过滤、自定义壁纸上传与 iOS 液态玻璃设置窗口。
d-dev0101/open-sea-skin★ 395
实时 WebGPU 海洋皮肤,可快捷调节波浪、日光、玻璃不透明度与自动昼夜循环。
RevolutionLA/dsh-dream-skin★ 250
一键换肤插件:8 套原创主题、背景壁纸(透明度/模糊)、强调色、主题包导入/导出+分享链接、收藏与随机,纯原生 token 系统接入。
kingOfSoySauce/dsh-liang-skin★ 244
自适应推理等级滑块皮肤,将当前模型可用档位映射到 0–30 视觉强度,并同步人物、背景和界面配色。
Nwflower/dsh-claude-style★ 173
为 DSH Web GUI 复刻 Claude Code Desktop 的视觉与交互——象牙白/暖黑双画布、陶烬橙强调色、三字体排版、权限分段控件与品牌切换,以 CSS 覆盖与客户端 DOM override 实现。
社区评论
评论公开保存在 GitHub Discussions。加载评论会连接 GitHub 和 Giscus;发表内容需要 GitHub 账号。