一个 DeepSeek Harness 外观插件:自定义主题色、背景壁纸图片 /…
30.7/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分
数据截至 2026/10/13 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
自动兼容检查有告警:npm 同名包「dsh-any-background」归属 tkingxiao/dsh-any-background,装到的可能不是本插件。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add lilcandi/dsh-any-background-plus该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:已验证
是什么
dsh 原生插件 · market
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 22 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查⚠ 装前注意npm 同名包「dsh-any-background」归属 tkingxiao/dsh-any-background,装到的可能不是本插件
展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包dsh-any-background @ 0.3.7
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/13 14:49:10
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
读写/删除本地文件发起外部网络请求
查看扫描证据(共 122 处,展示前 54 条)
- 读取本地文件lib/index.js:186
const raw = JSON.parse(await readFile(file, "utf8")); - 读取本地文件lib/index.js:424
await access(p); - 写入本地文件lib/index.js:447
await rename(p, expected); - 写入本地文件lib/index.js:737
await mkdir(dataDir(), { recursive: true }); - 读取本地文件lib/index.js:754
const st = await stat(file); - 读取本地文件lib/index.js:760
text = await readFile(file, "utf8"); - 写入本地文件lib/index.js:773
await rename(file, into); - 写入本地文件lib/index.js:812
await writeFile(tmp, JSON.stringify(normalized, null, 2), "utf8"); - 写入本地文件lib/index.js:813
await rename(tmp, configPath()); - 删除本地文件lib/index.js:819
await rm(tmp, { force: true }); - 读取本地文件lib/index.js:829
return (await stat(wallpaperPath())).size > 0 ? WALLPAPER_ROUTE : null; - 读取本地文件lib/index.js:837
return (await stat(wallpaperRightPath())).size > 0 ? WALLPAPER_RIGHT_ROUTE : null; - 删除本地文件lib/index.js:847
await rm(wallpaperPath(), { force: true }); - 写入本地文件lib/index.js:852
await writeFile(wallpaperPath(), Buffer.from(m[1], "base64")); - 发起网络请求lib/index.js:910
res = await fetch(url, { - 删除本地文件lib/index.js:1046
await rm(dshHomePath(DATA_DIR, ROTATION_DIR, removed.file), { force: true }); - 删除本地文件lib/index.js:1386
rm(tmp, { force: true }); - 删除本地文件lib/index.js:1513
await rm(wallpaperPath(), { force: true }); - 删除本地文件lib/index.js:1522
rm(tmp, { force: true }); - 读取本地文件pack-zip.mjs:15
for (const e of await readdir(dir, { withFileTypes: true })) { - 读取本地文件pack-zip.mjs:53
const raw = await readFile(f) - 读取本地文件pack-zip.mjs:118
for (const f of files) console.log(' ' + entryName(relative(stage, f).split(sep).join('/')) + ' ' + (await stat(f)).size) - 写入本地文件selfcheck.mjs:62
await mkdir(join(pics, 'nested'), { recursive: true }) - 写入本地文件selfcheck.mjs:63
for (const f of good) await writeFile(join(pics, f), `bytes:${f}`) - 写入本地文件selfcheck.mjs:64
for (const f of ['notes.txt', 'clip.mp4', 'noext', '.hidden.jpg.tmp']) await writeFile(join(pics, f), 'x') - 写入本地文件selfcheck.mjs:65
await writeFile(join(pics, 'nested', 'deep.jpg'), 'x') - 写入本地文件selfcheck.mjs:80
await mkdir(big) - 写入本地文件selfcheck.mjs:81
await Promise.all(Array.from({ length: 503 }, (_, i) => writeFile(join(big, `i${String(i).padStart(4, '0')}.png`), 'x'))) - 读取本地文件selfcheck.mjs:318
check('smoke: config now on disk', (await readdir(dataDir)).includes('theme-config.json')) - 读取本地文件selfcheck.mjs:368
eq('smoke: the first read paints the first name', await readFile(wallpaperPath, 'utf8'), `bytes:${listed[0]}`) - 读取本地文件selfcheck.mjs:374
const cfg = JSON.parse(await readFile(configPath, 'utf8')) - 读取本地文件selfcheck.mjs:381
eq('smoke: slot now holds the second image', await readFile(wallpaperPath, 'utf8'), `bytes:${listed[1]}`) - 读取本地文件selfcheck.mjs:411
const v3 = JSON.parse(await readFile(configPath, 'utf8')) - 删除本地文件selfcheck.mjs:497
await rm(rightPath, { force: true }) - 读取本地文件selfcheck.mjs:499
try { await stat(rightPath) } catch { rightExists = false } - 删除本地文件selfcheck.mjs:576
await rm(wallpaperPath, { force: true }) - 删除本地文件selfcheck.mjs:577
await rm(rightPath, { force: true }) - 删除本地文件递归selfcheck.mjs:679
await rm(root, { recursive: true, force: true }) - 读取本地文件unzip.mjs:16
const buf = readFileSync(zipPath) - 写入本地文件unzip.mjs:54
mkdirSync(target, { recursive: true }) - 写入本地文件unzip.mjs:57
mkdirSync(dirname(target), { recursive: true }) - 写入本地文件unzip.mjs:58
writeFileSync(target, method === 0 ? raw : inflateRawSync(raw)) - 读取本地文件verify-host.mjs:51
const creds = readFileSync(CREDENTIALS, 'utf8') - 读取本地文件verify-host.mjs:75
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) - 发起网络请求verify-host.mjs:84
const doc = await fetch(BASE + '/', { headers: AUTH }) - 发起网络请求verify-host.mjs:90
const res = await fetch(`${BASE}/${combo[1]}`, { headers: AUTH }) - 发起网络请求verify-host.mjs:100
const res = await fetch(`${BASE}/dsh-any-background/dshAnyBackground/${method}`, { - 读取本地文件verify-host.mjs:141
const names = readdirSync(rot.folder).filter((n) => /\.(jpg|jpeg|png|gif|webp)$/iu.test(n)) - 读取本地文件verify-host.mjs:144
const size = statSync(join(rot.folder, n)).size - 读取本地文件verify-host.mjs:151
if (sha(readFileSync(join(rot.folder, n))) === hash) return n - 读取本地文件verify-host.mjs:160
for (const n of readdirSync(dir).filter((n) => /\.(jpg|jpeg|png|gif|webp)$/iu.test(n))) { - 发起网络请求verify-host.mjs:201
const served = await fetch(BASE + '/dsh-any-background/wallpaper') - 发起网络请求verify-host.mjs:324
const res = await fetch(BASE + '/dsh-any-background/wallpaper-right') - 发起网络请求verify-host.mjs:327
const post = await fetch(BASE + '/dsh-any-background/wallpaper-right', { method: 'POST' })
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/dsh-home-paths@deepseek-ai/cordis@deepseek-ai/dsh-client-locale@deepseek-ai/dsh-client-store@deepseek-ai/dsh-client-connection@deepseek-ai/dsh-client-ui-settings@deepseek-ai/dsh-client-ui-sidebar-right@deepseek-ai/dsh-client-ui-theme@deepseek-ai/dsh-invariants给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- 左右双图轮播
- 避开中间会话区
- 左右各一文件夹
dsh-any-background 的增强分支:左右双图轮播(避开中间会话区)+ 左右各一个文件夹
README
由 DeepSeek 最新模型翻译生成dsh-any-background-plus
这是一个 fork,而其中这一半是我做的。 修改自 Tkingxiao/dsh-any-background(MIT)——色轮、逐表面透明度/模糊、动态生成背景与轮播引擎由上游作者原创。从 v0.4.0 起的全部改动均为本 fork 自作,详见下方本 fork 做了什么。上游仓库:github.com/Tkingxiao/dsh-any-background。
一个 DeepSeek Harness 外观插件:自定义主题色、背景壁纸(图片 / 算法动态生成)、逐表面的透明度与模糊度控制——外加本 fork 的左右双图轮播、双文件夹轮播来源和右下角倒计时切换按钮。兼容 DSH 0.1.5-rc.2 ~ 0.2.1-alpha.2。
本 fork 做了什么
以下是我在上游基础上自己实现的功能,也是这个 fork 存在的理由。
- 左右双图轮播——两张图,一左一右。 一个 dual 开关即可让同一轮播的两张不同图片同时铺满窗口两侧,避开中间的会话栏。动机是几何上的而非装饰性的:壁纸画在整个应用后面,而宿主的会话栏压在正中央,所以一张主体居中的图,它的主体恰好落在看不见的位置。两条车道改为贴着左右边缘,原本被遮掉一半的那张图,反而成了你真正在看的那张。默认关闭。
- 一次轮播推两步,而不是开第二个轮播。
展开完整文档(README 剩余 1 节)
dsh-any-background-plus
这是一个 fork,而其中这一半是我做的。 修改自 Tkingxiao/dsh-any-background(MIT)——色轮、逐表面透明度/模糊、动态生成背景与轮播引擎由上游作者原创。从 v0.4.0 起的全部改动均为本 fork 自作,详见下方本 fork 做了什么。上游仓库:github.com/Tkingxiao/dsh-any-background。
一个 DeepSeek Harness 外观插件:自定义主题色、背景壁纸(图片 / 算法动态生成)、逐表面的透明度与模糊度控制——外加本 fork 的左右双图轮播、双文件夹轮播来源和右下角倒计时切换按钮。兼容 DSH 0.1.5-rc.2 ~ 0.2.1-alpha.2。
本 fork 做了什么
以下是我在上游基础上自己实现的功能,也是这个 fork 存在的理由。
- 左右双图轮播——两张图,一左一右。 一个 dual 开关即可让同一轮播的两张不同图片同时铺满窗口两侧,避开中间的会话栏。动机是几何上的而非装饰性的:壁纸画在整个应用后面,而宿主的会话栏压在正中央,所以一张主体居中的图,它的主体恰好落在看不见的位置。两条车道改为贴着左右边缘,原本被遮掉一半的那张图,反而成了你真正在看的那张。默认关闭。
- 一次轮播推两步,而不是开第二个轮播。 两条车道来自同一个轮播的同一次推进:左侧下标由既有的 pickRotationIndex() 抽取(沿用相同的乱序/顺序语义,以及“不与当前图重复”规则),右侧下标则由 nextLaneIndex() 得出——它从左图向前迈一步。之所以是“迈一步”而不是“再抽一次”是刻意的:乱序随机数没有种子,两次独立抽取偶尔会撞成同一张图,而那恰恰是双图模式要避免的。在 order 模式下,这对图就是相邻的两张。选中的一对以 laneItems 持久化,所以它既能扛过刷新,也不会左右错位。
- 第三种轮播来源:左右各一个文件夹。 轮播可以为每条车道读取不同的目录,各自就地列举、就地推进。频率与模式仍然共用——重点是两个来源,而不是两个轮播——所以左右游标是分开保存的:左侧游标就是存下来的 current,右侧游标则通过“右车道当前显示的这张图在它自己的列表里是哪一项”反查得到。这一点在两个目录长度不同时立刻变得关键,因为单一共享下标会把较短的那个列表走越界。只有两个目录都就绪时墙面才会变化;只选了一个时只保存、不预览,这样墙面永远不会停在“半边是旧图”的尴尬状态。单文件夹模式仍然保留——这个“一对”是第三种来源,而不是替代品。
- 分界线是窗口的绝对中心,且两条车道在构造上等宽。 wpLeftEl 与 wpRightEl 是对等的 position:fixed; width:50%; z-index:-1 元素,各自钉在一边,并被赋以完全相同的像素宽度——Math.floor(innerWidth * 0.5)——因此奇数宽度的视口也不可能让某一条车道多出一个像素。让图片够到接缝的那点外扩只在 center 模式下施加:在 fit 模式下图片在构造上就被限制在自己的车道内,此时再外扩会让两条车道在接缝处重叠,后画的那条赢走那条窄带——一张图肉眼可见地侵入了另一张,而这正是本模式要消除的不对称。
- 两张图的实际尺寸是分别测量的。 车道几何需要两张图的真实尺寸才能排版,而单槽位的测量缓存供不了这个——后跑的那条车道会占据缓存,把先跑的那条挤进按比例的兜底路径,结果就是一张按自己的框画、另一张按自己的比例画。那恰恰是双图模式要避免的“一张大一张小”,所以尺寸放在一张按 URL 索引的小映射表里同步查。
- 边缘淡化对两条车道一视同仁。 滑块按每张图自身的比例读数,渐变用同一套像素级配方针对同一个框计算,并且无条件施加——绝不会因为某条车道比它的框更宽就跳过它。因此两条车道对称淡化,不可能出现一条淡、另一条还是硬边的情况。
- 设置页预览也照搬这个左右分屏。 主题面板上的主图按同样的 50/50 网格绘制,右侧车道的图就摆在左边那张旁边,所以预览展示的是墙面真实的样子,而不是一张居中的图。右侧 URL 走主题 store 传递,而不是去读模块级的图片状态——因为 React 表面直接读那个状态会错过轮播更新。
- 右下角倒计时切换按钮。 一个圆形按钮钉在视口右下角,不必打开任何面板就能触达,周围画一圈实时进度环,显示距离下次换图还有多久。圆环只在 minutes 定时模式下出现——那是浏览器唯一能自己算出剩余时间的间隔(reload 每次读取都算到期,daily/weekly 由 Node 半部在页面加载时定夺,而关闭的轮播根本不会到期),所以这些情况下只画按钮,而不是画一个永远显示“满”的、等于撒谎的环。点击即刻换图,而因为推进本身会写入 lastRotate,环会自行从满重新开始。它作为独立的 React 根挂载并 portal 到 <body>,所以设置对话框的滚动容器或宿主的 transform 都裁不掉它。按住可以把它拖到任意位置(位置按窗口记住,每次改窗口大小都会重新夹回视口内),右键一下送回角落。它的外观直接取宿主自己那个浮动按钮的配方,而不是照着描一个——--dsw-alias-button-floating-fill / -hover、--dsw-elevation-panel、--dsw-elevation-stroke-color: var(--dsw-alias-border-l3) 与 --dsw-alias-label-primary,正是宿主「回到底部」控件解析到的那套令牌,所以它按构造就跟着主题色和明暗切换走。若改成挂到 ——它是 <body> 的兄弟节点,也就是落在所有设计令牌的另一侧——按钮会只剩一圈透明轮廓;这是量出来的,不是猜的。
- 视频功能是按代码删除的。 每一个视频端点、路由、上传处理器、mime 表、文件名白名单、文件夹扫描器、逐元素播放层及其设置,都是删除而非隐藏——BackgroundType 不再包含 'video',轮播不再包含 media / videoFolder / videoName / videoVolume / videoAlign / ended,整个 src/client/utils/video.ts 模块已移除。仍在配置里写着视频轮播的旧存档,会在规范化时回退到图片。
- 能扛过宿主升级的兼容范围。 宿主会在导入任何模块之前按 peerDependencies 拦截插件,所以一串写死的版本号等于一张硬性到期日:宿主发一个它没列名的新版本,插件在加载阶段就被拒,接着被下一次插件管理器写操作从 dsh.profile.bundles 里剔掉。现在这道闸是 semver 区间(>=0.1.5-rc.2 <0.3.0-0),而 dsh.compatibility.dshReleases 继续保留「真的核对过哪十个 release」的显式记录,直到 0.2.1-alpha.2。对比 0.2.0-rc.1 与 0.2.1-alpha.2 的包后确认 ThemeRuntime 与 SidebarRightTabRegistry 未变,因此本次构建不需要新增适配器目录——它并入事实依然成立的那个目录。
截图
左右双图轮播 · 同一轮播的两张不同图片,以窗口绝对中心为界,避开会话栏
主题设置面板的背景页 · 图片来源、文件夹选择、布局模式与壁纸轮换
主题色与逐表面透明度 · PS 风格色轮、智能取色、逐表面滑块
右下角切换按钮 · 宿主原生浮动按钮样式,可拖动、右键归位,定时模式下围绕一圈倒计时
功能特性
- PS 风格色轮 — 在色相环上选取色相,在内嵌方形中调整饱和度与明度,实时生成 30+ 个 CSS 设计令牌。
- 精确 HSL / RGB 输入 — 通过数值精确输入颜色,与色轮实时双向同步。
- 智能取色 — 一键从壁纸提取主题色:采样可见区域、量化像素、剔除灰色/近黑/近白,选出现量最大的鲜亮色。纯客户端完成。
- 吸管取色 — 在壁纸上悬停预览颜色,点击即可选为主题色。
- 背景壁纸 — 上传任意图片作为壁纸,在视口比例的编辑器中拖动平移、滚轮缩放(触屏上单指拖动、双指捏合缩放)。
- 位置编辑器 — 拖动平移、滚轮或双指缩放、一键重置;各槽位(左右车道的图片)的位置状态各自独立保存,互不覆盖。
- 布局模式 — 适应 / 填充 / 拉伸 / 平铺 / 居中五种排布;「适应」模式下编辑器提交的构图在窗口缩放、跨屏移动后保持一致。当图片比例与窗口不一致时(「适应」/「居中」会留出余量),余量由同一张图的模糊副本填充,不会出现死黑边;「边缘淡出」滑块(0–60%)再把清晰图片的边缘羽化进这层填充,默认关闭。
- 动态生成背景 — 支持网格渐变、Shader、几何图案,可调节扩散范围、色彩强度并锁定种子。
- 分部位界面透明度 — 主背景、侧边栏、卡片面板(含对话框周围的选项框/菜单)、输入框与控件(发送框、Cordis 插件面板)、设置面板、对话文本框、轨迹页、右方侧边栏(或 bettersidebar)、产出物/高亮内容,以及顶栏选项(Agent Team 面板、后台任务列表与会话头部下拉菜单)各自独立滑块。
- 分部位界面模糊度 — 每个界面部位可独立调整毛玻璃 backdrop-filter 模糊(0–60 px),并通过宿主的稳定选择器为发送框、Cordis 面板与弹出层提供真实背景模糊。
- 产出物 / 高亮内容 — 对话正文里的代码块(含语言顶栏)、行内 code 高亮芯片与产出物 chip 共用一支滑块:透明度只调制这些表面原本的背景色(不会在旧背景上再叠一层新色),模糊度给同一层表面加磨砂,壁纸从内容后方透出来。
- 右方侧边栏 / bettersidebar 表面 — 同一对滑块(panelOpacity / blurs.panel)按宿主环境自动切换目标:未安装 dsh-better-sidebar 时叫「右方侧边栏」,驱动官方右侧栏的表面令牌与毛玻璃模糊(0.1.5-rc.2 至 0.1.7 均适用);安装了 dsh-better-sidebar 时改叫「bettersidebar」,接管其底部工作台面板(官方右侧栏同样生效)。该行现在始终显示。
- 顶栏选项 — 会话头部的下拉浮层获得了独立的透明度 + 模糊滑块:Agent Team 面板、后台任务列表、「用其它应用打开」与「会话日志」菜单、子代理血缘树。由于这些浮层被 portal 到 <body>(与头部 DOM 断开),且 0.1.7 把「用其它应用打开」改成 portal、把会话行菜单改成动态槽位,插件改为在运行时监听稳定的 conversation.session.header 槽位锚点来标记当前展开的浮层,不再依赖类名形状。
- 侧边栏「主题」页面(双模式) — 同一套五页(色彩 / 界面 / 字体 / 背景 / 配置)注册到两处:未安装 dsh-better-sidebar 时,通过官方右侧栏的公开扩展点(sidebarRightTabs + sidebar.right.pane.tab)在官方侧边栏的引导页挂一张「主题」卡片,点开即五页;安装了 dsh-better-sidebar 时,改为在它的侧边栏注册同名页面,官方引导页上的卡片自动撤下,避免重复。两处(加设置面板共三处)共用同一套页面代码与同一份状态,改一处处处同步;外壳按面板宽度自适应,窄面板收紧内边距并回到单列。宿主没有右侧栏扩展点时静默不注册,不影响任何其它功能。
- 对话视图卡片 — 消息列表自动包裹为半透明卡片,轨迹页可整页调节透明度与模糊,让壁纸从内容后方透出来。
- 主题导出 / 导入 — 一键导出为自包含的 dsh-any-theme.json(配置 + 壁纸,以 data URL 内嵌),可随时导入还原。
- 外观预设与配置档案 — 六套一键预设(默认 / 毛玻璃 / 极简白 / 暗夜紫 / 赛博 / 暖阳),外加自定义命名档案:随时保存当前观感、随时套回,删除有二次确认保护。
- 壁纸轮换 — 两种来源任选:轮换池(把多张图片加入轮换池,带缩略图选择器)或图片文件夹(一键调起系统的「选择文件夹」对话框,直接读取该目录里的文件,不复制、不额外占用空间;读取 jpg / png / gif / webp)。按随机或顺序、每次刷新 / 每 N 分钟(1–1440)/ 每天 / 每周的频率自动更换(分钟档位由页面内的定时器驱动,需要页面保持打开;每天 / 每周这类按日历推进的档位则由服务端在你下次打开时结算);切换时服务端只把所选那一张复制进当前壁纸槽,导出/导入与取色管线完全不用改。宿主不提供原生文件夹选择能力时,按钮自动隐藏而不是报错。
- 昼夜自动切换 — 指定日间与夜间两套配置,按固定时段或跟随系统深色模式自动切换。
- 自定义字体 — 上传一个 ttf / otf / woff / woff2 字体文件(上限 100 MB),通过 @font-face 应用到整个界面的文字;可随时停用或移除。字体按原始字节流式上传并持久化到插件数据目录,代码块仍保持等宽字体。
- 分组文字描边 — 沿用界面页的分组(现为十组,含顶栏选项),为每个分组单独加 -webkit-text-stroke 描边:粗细 0–4 px(0 = 关闭),颜色支持自动反色 / 灰 / 黑 / 白 / 主题色 / 自定义。代码块、inline code、图标,以及宿主那类 background-clip: text 的流光文字(「深度求索中」状态行与轮次过程行)自动豁免——多色语法不会糊成一团,渐变文字也不会被描边压成一坨纯色。
- 强制界面明暗 — 无论主题色明暗如何,都能强制生成亮色/暗色整套令牌;「自动」下配色与字体方向由主题色明度驱动(深色 → 浅字,浅色 → 深字),未选主题色时按壁纸画面亮度判断。
- 文件持久化 — 所有设置保存到文件系统 ~/.dsh/.dsh-any-background-data/,不再依赖 localStorage。
- 中英双语 — 完整的中英文界面,自动跟随语言设置。
- 主题守护 — 宿主重置主题后自动重新激活自定义主题。
更新日志(最新在前)
v0.4.5(兼容范围不再是一串手打的版本号)
- 兼容范围现在真的是「范围」,而不是九个手打的版本字符串。 engines.dsh、七个受闸的 @deepseek-ai/dsh- peerDependencies 与 dsh.compatibility.dsh 原本是 0.1.5-rc.2 || 0.1.5-rc.3 || … || 0.2.0-rc.1 这样的枚举——宿主每发布一个表外的新版本,都得先改它、重建、重新发一版,插件才能跑。现在统一为 >=0.1.5-rc.2 <0.3.0-0。上界是刻意留的:<0.3.0-0 放行全部 0.2.x 预发布版——-0 后缀的作用就是让预发布边界变为包含,于是 0.3.0-alpha.1 被挡在外面、0.2.1-alpha.9 不会——同时仍然止步于 0.3.0,那一版预期宿主形状会再变一次。
- 为什么这是真故障、不是清理工作:在 DSH 0.2.1-alpha.2 上,那份枚举覆盖不到正在运行的 release,于是 evaluatePluginCompatibility 返回 issue、loadProfileDirectory 把它扔进 skippedBundles——静默地,不打印、也不动 manifest。之后某次插件管理器的写操作触发 reconcileProfilePlugins,它的保留规则是「依赖只有仍被判定为 bundle 才留」,于是 dsh-any-background 被整个从 dsh.profile.bundles 里剔除。用户看到的现象就是插件干脆不在了。放宽范围解决的是前半段;后半段是宿主侧行为,插件影响不了,所以 profile manifest 里那一条也得补回去。
- dsh.compatibility.dshReleases 保留显式列表,并新增 0.2.1-alpha.2。 那张表是「哪些版本真的核对过」的人读记录,所以它继续是枚举——写成一个范围就等于宣称覆盖了一堆没人验过的东西。semver 范围是机器闸;这张表是关于「看过哪些」的声明。
- 用宿主自带的 semver 验过,不是凭直觉:拿宿主打包的 semver@7.8.5 加 {includePrerelease: true} 对这条范围跑 semver.satisfies——0.1.5-rc.2 到 0.2.1-alpha.2 全部满足,0.3.0-alpha.1 与 1.0.0 不满足。
- SUPPORTED_RELEASES 新增 0.2.1-alpha.2 一行,于是适配器注册表对正在运行的宿主报告 exact,不再夹到 nearest 并在每次启动时打一行夹取警告。这一行并入 0.1.7-alpha 目录,因为 0.2.1-alpha.2 的包里 ThemeRuntime 与 SidebarRightTabRegistry 仍是那个目录记录的形状——是新增一行,不是新增一个适配器目录。这是适配器表,不是加载闸:classifyRelease 对不认识的版本返回 match: 'nearest',那只是一行日志,从不构成拒绝。
v0.4.4(按钮能拖了,而且按钮就是宿主自己那个按钮)
- 按钮可拖动,位置能扛过刷新:按住拖到任意位置即可。拖动走指针捕获,超过 4 像素的位移阈值才把控制权交给按钮、而不是留给页面;松手时把算出来的 {x, y} 写进 localStorage 的 dab-orb-pos——不写进插件配置。这个取舍是刻意的:位置属于这块屏幕上的这个窗口,不属于主题;写进配置还会把 Node 半部拖进一个纯显示的事情里,改一次得重启宿主才生效。角落仍是默认,而且在 CSS 里就一直是默认——只有在存过位置时才去读它,右键点击按钮会删掉这条记录,把按钮送回角落,而不是送回一个「接近角落」的钉死坐标。存下来的位置会在每次窗口尺寸变化后重新夹回视口内(按钮把上次量到的尺寸记在 ref 里,所以第一帧也能夹),这才拦得住「在宽屏上存下的位置,到窄屏就把按钮停到屏幕外」。
- 按钮现在就是宿主那个浮动按钮,而不是仿制品:样式逐条抄自 ChatView.module.css 里那个自带的 EvIC1a_toBottom「回到底部」控件——填充用 --dsw-alias-button-floating-fill(悬停用 -hover),阴影用 --dsw-elevation-panel,那条发丝描边由 --dsw-elevation-stroke-color: var(--dsw-alias-border-l3) 提供,图标色用 --dsw-alias-label-primary,border-radius: 100px 配 corner-shape: round,34px 见方。旧按钮是手挑的深色圆角方块;新按钮取的是宿主自己的浮动控件同样解析到的那套令牌,于是主题色、明暗切换、阴影配方都跟着走,一条都不用复述。这是在活页面上量出来的、不是推的:computed border-radius: 100px、corner-shape: superellipse(1)、cursor: grab,图标色与 --dsw-alias-label-primary 完全相等。
- 改样式时顺手挖出一个潜伏的缺陷:@keyframes dab-orb-in 原先用 animation-fill-mode: both,也就是说动画结束后最后一帧仍一直挂着——那句收尾的 transform: none 于是永远钉住按钮的 transform,:hover 与 :active 的缩放都不可能生效。现在填的是 backwards,这才是入场动画要的:动画开始前保持首帧,结束后把属性彻底交还。
- 减弱动效的那段样式根本够不到这个按钮:它被限定在 .dab-root 里,而按钮被 portal 出这棵子树、挂到了 <body> 上,于是唯一一个「每次轮播都动、每秒还要重绘一次」的元素反倒不受这个偏好约束。现在那段 media 块里单独写出了它的选择器。
- Portal 改挂 <body>,这是真缺陷、不是洁癖:所有 --dsw-alias- 令牌只声明在 body / body[data-ds-dark-theme] 上(实测 209 处声明,没有 :root、也没有 html),于是挂到 document.documentElement 的 portal 宿主——它是 <body> 的兄弟而不是祖先——一个令牌都继承不到,全部落空:提示条的 background: var(--dsw-alias-bg-layer-3, var(--dsw-alias-bg-layer-2)) 解析不出任何值,整条提示全透明、边框变黑,只有 z-index: 10001 还生效。验证方式是把真的 .dab-toast 类分别放进两种父节点的仿造 portal 宿主里量:挂 <body> 的算出 background: rgba(92, 95, 69, 0.5)、border: 1px solid rgba(117, 119, 95, 0.22);挂 的算出 background: rgba(0, 0, 0, 0)、两个令牌查询都是空。按钮当初也是这么挂的、同样取不到令牌——它在 v0.4.3 看着正常,只是因为那次把默认样式重写成了写死的值。
- 提示语把操作方式教给你:悬停提示现在是 立即切换壁纸 · 还剩 1:21 · 拖动可移动,右键归位——原有的倒计时后面接上拖动/右键那一句,中英双语同步。
v0.4.3(真实浏览器里发现的两个缺陷,且都是构建链看不见的)
- 按钮成了左上角的裸方块:它的全部外观——圆形、固定在右下角、那圈倒计时——都住在设计系统的样式表里,而 ensureUiCss() 原先只有一处调用:ThemeSection,它只在打开设置对话框时才挂载。按钮却在插件启动时就进了 DOM,于是既没有 position:fixed、又落在 portal 宿主里,退化成浏览器默认样式。现在 mountRotateOrb 在造按钮之前自己先调 ensureUiCss();该调用幂等,之后再打开设置页只多一次字符串比较。
- 点击按钮毫无反应:点击处理函数去 storeInstance.actions 上取 rotateNow,但那个对象只带 defineStore 块里声明的三个同步动作——rotateNow 是 buildFace 后来给设置页加的,是另一份对象。取值得到 undefined,守卫静默返回,于是「函数不存在」和「按钮坏了」在表现上完全一样。设置页那个「立即切换」按钮自始至终是好的,正是这一点证明轮换逻辑本身没问题,把故障锁定在浮动按钮的动作来源上。现在 rotateOnceNow 作为必填参数传入,把这种状态变成不可表达、而不只是修掉;另加 .catch,将来真有失败会走 console.error,不再被吞掉。
- 那个误导性的可选成员已删除:BoundActions.rotateNow? 是我追第一个猜测(「绑定的动作是旧的」)时加的,而那个猜测是错的——它恰好引诱出导致第二个缺陷的静默取值。连同「store 与设置槽交出同一份动作对象」那句错误注释一并删除。事实并非如此。
v0.4.2(左右双图轮播:一张左、一张右)
- 视频功能已按代码删除:所有视频端点、路由、上传处理器、mime 表、文件名白名单、文件夹扫描、逐元素播放层及其设置全部删掉,而不是隐藏——BackgroundType 不再含 'video',轮播不再含 media / videoFolder / videoName / videoVolume / videoAlign / ended,整个 src/client/utils/video.ts 模块已删除。壁纸重新只剩图片、渐变网格、着色器、图案四种,也就是前三个版本一直围绕的东西。仍写着视频轮播的旧配置会在归一化时回落到图片:白名单已经去掉了那个词,所以这个开关读出来是「关」,而不是留下一个画不出东西的模式。
- 左右双图轮播:轮播面板新增 dual 开关,同一轮播的两张不同图片同时铺在视口左右两侧、避开中间会话栏。动机是几何上的,不是装饰上的:壁纸铺在整个应用背后,而宿主的会话栏压在中段之上,一张主体正好居中的图片,主体恰好落在看不见的地方。两条车道改贴两边,原本被挡掉一半的那张图,现在才是真正被看到的那张。
- 是同一个轮播推进两次,不是第二个轮播:两条车道来自同一次推进——pickRotationPair() 用既有的 pickRotationIndex() 取左道下标(同样的洗牌/顺序语义,同样的「不重复当前这张」),再用 nextLaneIndex() 取右道下标,后者是从左道往前走一步。走一步而不是再抽一次是刻意的:洗牌的随机数没有播种,两次独立抽取偶尔会撞在一起、把同一张图画两遍,而这恰恰是双图模式存在的理由。order 模式下这一对就是相邻的两张。选中的结果以 laneItems 持久化——两条车道各自的图片名,按被绘制的顺序排列——所以这一对能扛过刷新,也不会彼此走散。
- 两条车道是两个独立的磁盘槽位:右侧面板有自己的文件(~/.dsh/.dsh-any-background-data/wallpaper-right.jpg)和专属服务路由(/dsh-any-background/wallpaper-right),与原有的 wallpaper.jpg 并列,而不是把一张图裁成两半。每个面板在自己的框里按自己的算术绘制——尤其右道的 contain 算术是按半个视口宽算的,因为它是一张图放在自己那一半里,而不是一张图的一半:先按整窗 contain 再裁切,会把主体的右侧吃掉,而那正是这个功能要保住的东西。候选池只有一张图、或 dual 关掉时,服务端根本不回报右侧 URL,浏览器半就以此为准撤掉第二个面板,而不是留一张旧图挂在上面。
- 分界是窗口的绝对中心,两条车道在构造上等宽:wpLeftEl 与 wpRightEl 是一对对等的 position:fixed; width:50%; z-index:-1 元素,各自贴住一侧,并且拿到完全相同的像素宽度——Math.floor(innerWidth 0.5)——所以视口宽度是奇数时也不会有一条车道宽出一个像素。整视口层在双图模式下保留但隐藏,只因为效果层仍要通过它写模糊与不透明度。让图片够到接缝的那点外扩只在 center 模式下施加:fit 下图片本来就装在车道内,此时再外扩会让两条车道在接缝处重叠,谁后画谁赢得那一条——视觉上就是一张图压着另一张,而这正是这个模式要避免的不对称。
- 两张图的原始尺寸分别测量:车道几何需要两张图各自的真实尺寸都在布局前拿到,而单槽测量缓存做不到——后运行的那条车道会占据缓存,把先运行的那条推进按比例兜底,于是一张图按自己的盒子铺开、另一张按比例缩小。这正是双图模式要避免的「一张比另一张大」,所以尺寸改由一张按 URL 索引的小表保存,并提供同步查询。
- 边缘淡化对两条车道一视同仁:滑块按每张图自身的比例读取,坡道用同样的 px 停靠配方、对着同样的盒子计算,并且恒定施加(不会因为某条车道比自己的盒子宽就跳过)。因此两条车道对称羽化,不会一条淡出、另一条还是硬边。
- 两条车道是并列的兄弟元素,让位的只是整视口层:wpLeftEl 与 wpRightEl 处于同一个层叠上下文中的对等位置,各自贴住自己那一侧;原本的整视口层在双图模式打开时被设为 visibility:hidden 而不是被切成两块——这样单图取景路径、边缘羽化和拖动降采样对仍然需要它的一件事保持完好,也就是效果层通过这个隐藏层继续写模糊与不透明度。关掉 dual、采用手动单图、切到生成式背景、或整体拆除墙面,都会移除两条车道,所以任何一次模式切换都不会残留旧图。
- 第三种轮播来源:左右各一个文件夹:轮播现在可以为每条车道读取不同的目录,各自列出、各自就地推进。频率与模式仍然共用——重点是两个来源,不是两次轮播——所以左右游标分别保存:左游标就是存下来的 current,右游标则通过在它自己的目录里反查右道正在显示的文件名来恢复。一旦两个目录张数不同,这一点就变得关键:共用一个下标会把较短的那个列表走出边界。只有当两个目录都存在时墙面才会变化;只选了一个时会存下来但不预览,所以墙面永远不会一边已经换了、另一边还挂着旧图。单选文件夹模式依然保留——这一对是第三种来源,不是替代品。
- 设置页预览镜像同一分界:主题面板的预览大图按同样的 50/50 栅格绘制,右道的图片就摆在左道旁边,所以预览看到的就是墙面实际的样子,而不是一张居中单图。右道 URL 经由主题 store 传递,而不是直接读模块级图片状态——React 面直读会漏掉轮换更新。
- 面板与文档:轮播那一行新增「左右双图」/ "Left + right" 开关,就地切换该模式,并附一行说明它做什么,中英双语(两侧键集合保持一致)。
v0.4.1(视频播完再轮播、可调音量)
- 「播完就换」成为第五种轮播频率:视频轮播可以在每次刷新 / 每 N 分钟 / 每天 / 每周之外,再选一个播完就换——视频播放到结尾时立刻切下一个,不看时钟。这条档位由播放器的 ended 事件驱动(浏览器把播放结束回报给轮播层),因此它与循环播放是互斥的:在该档位下 loop 必须关掉,否则 ended 永远不触发,一个「放完再换」会被悄悄退化成「第一段无限循环」。也正因为没有时钟,它在按时间戳判断是否到期的路径上一律返回「未到期」——否则一次刷新就会跳过第一段。图片轮播不显示这个档位:图片没有播放结束可言。若某个视频文件夹的轮播原本选中了它,之后文件夹被清除、轮播回落到图片,这个档位也会一并回落为「每天」,不会留下一个永远不会触发的频率。
- 视频音量可调(0–100,默认静音):视频轮播面板新增音量滑块,值随配置持久化,重启与切换配置都保留。规则很简单——0 就是静音,非 0 就按百分比设置元素音量;之所以要这样分,是因为壁纸视频没有用户手势,浏览器只允许静音的自动播放,所以默认 0 是能自动播起来的前提,想要声音得自己把滑块拉上去。音量在每次重绘时都会重新写进 (而不是只在创建元素时设一次),这样拖动滑块能立刻作用到正在播放的那个视频;超出范围的值在两端被夹紧(元素音量大于 1 会直接抛错)。滑块只在选中的是视频文件夹时出现。
v0.4.0(视频文件夹轮播与主题色余量填充)
- 视频文件夹纳入轮播:壁纸轮播现在有三种来源——图片轮换池、图片文件夹、视频文件夹。视频文件夹就地读取、就地流式播放:一个字节都不复制,因此动辄几十 GB 的收藏对数据目录零占用,切换视频只是一次改文件名。可识别扩展名为 mp4 / m4v / webm / ogv / ogg / mov / mkv;若所选目录里一个视频都没有,选择器会直接拒绝(no videos),而不是采纳一个永远不会播放的轮播。图片与视频刻意共用同一套引擎与同一个开关:选视频文件夹写入 media: 'video',选图片文件夹或轮换池写回 'image',因此永远只有一套频率、一套随机/顺序、一个时间戳需要推理。视频文件夹若已失效(移动硬盘拔了、目录被删),轮播会在规范化时回落到图片,而不是持久化一个「看着是开的、实际什么都不播」的状态。
- 视频的比例余量用主题色模糊填充:图片那条路会用同一张图的模糊副本填补比例差异,而视频不抓帧就拿不出副本。于是背景层改画一道由实时主题色推导的双色标 linear-gradient(用主题同一套 hsl(),第二色标旋转 40°、明度降 12%),沿用同样的 30 px 模糊与同一个不透明度滑块;且只在真正会留白边的模式(适应 / 居中)下出现。零解码开销,主题色变化时同步跟随。
- resolveServedVideo() 夹在两个视频来源之间:/video 路由现在优先解析轮播的 videoFolder + videoName,无果才回落到上传的单槽位,因此轮播文件夹为空也不会把本来能用的壁纸搞黑。持久化的文件夹是绝对路径,持久化的文件名是裸文件名——safeVideoName()(扩展名白名单,拒绝 / 与 \)在存储时已保证不含分隔符,所以这个 join 无法逃出所选目录,该白名单就是全部边界防护。
- 轮播间隔可自定义,旧字段自动迁移:minutes5 原是固定的 5 分钟档;现在轮播存 interval: 'minutes' 加一个 1–1440 的 intervalMinutes,而磁盘上遗留的 minutes5 会在加载时迁移为 minutes/5,已有配置行为不变。页面 30 秒轮询未改,因此即使把间隔调到 1 分钟,最坏延迟仍是 30 秒。
- 验证脚本加了「进程陈旧」闸门:verify-host.mjs 现在先判断运行中的进程是否真的带上了该功能再探测——Node 半在启动时载入内存,所以在本次构建之前启动的宿主会用 unknown endpoint 回答新端点;对一份磁盘上完全正确的代码报三条失败,比没有更糟。该探测现在会说明宿主是从哪个构建启动的,并给出补救办法。
v0.3.1(版本适配隔离:每个宿主版本一个目录)
- 前置版本检测层:宿主 release 的探测与渠道归类收进 src/host-compat/(Node 半侧读取进程所由以启动的那份 @deepseek-ai/dsh/package.json,即 ctx.profileContext.installAnchor,拿不到时再退回启动器 homes/<ver> 旁边 versions/<ver> 里的同名清单与目录名,经 read RPC 下发),客户端侧由 src/client/host-compat/ 承接判定、广播变更,并在判定到达时重切静态样式表。原本散落在 src/client/host.ts 与各消费方里的版本判定统一到这一层,该模块已删除。
- 每个版本一个独立目录:src/client/host-compat/versions/ 下 v0-1-5-rc-2-3 / v0-1-6-alpha-1-2 / v0-1-7-alpha-1-2-rc-1 / unknown 各自描述本版本的面板力学(提升与模糊挂在哪一层)与头部槽位键,versions/registry.ts 是全插件唯一的「release → 代码」映射。底码只向适配层提问(引导页表面归谁、用哪些槽位选择器),不再出现版本字符串比较——适配新宿主等于新增一个目录并登记,底码不用动。
- 版本标识细化到发布渠道:适配键不再是 0.1.5 这种补丁号,而是「补丁行 + 预发布渠道」的 0.1.5-rc / 0.1.6-alpha / 0.1.7-alpha。理由是每个目录的力学都是照着某个具体渠道的 tag 逐条核对出来的,把 0.1.5-rc.3 与假想中的 0.1.5-beta.1 记成同一个键,等于让未核对过的形状继承已核对过的结论。目录名与适配器 id 现在也直接写出核对过的具体 tag(v0-1-6-alpha-1-2,id 报 0.1.6-alpha.1/alpha.2),不打开目录就知道这张表覆盖到哪一步;同一条补丁行内两个构建确有差别的地方(leading 槽位与插件管理页都只在 0.1.6-alpha.2 起存在)由该目录自己带预发布门控。
- 落在已核对范围外的宿主按就近原则取档:src/host-compat/channel.ts 里那张 SUPPORTED_RELEASES 表(0.1.5-rc.2 / 0.1.6-alpha.2 / 0.1.7-alpha.2 / 0.1.7-rc.1,从旧到新——一行记一个核对过事实的构建,所以同一条补丁行可以有两行)先按补丁行匹配:本机是 0.1.6-alpha.4 而表上只核对到 0.1.6-alpha.2 时,用的仍是 0.1.6-alpha 这一档(渠道序号只是同一条线里的构建计数,0.1.6-alpha.1 比核对基准更早的情况另有适配器就近处理);0.1.6-beta.1、乃至假想中不带渠道的 0.1.6 也算同一条线,落回该线。整条补丁行都不在表上时才收边:高于最新一档取最新,低于最低一档取最低,夹在两档中间取较低的那一档(适配层只声明它核对过的东西)。这类收边判定会打一条日志说明「本宿主不在核对范围内,按 X 档处理」,新宿主报问题时应先看这行。只有完全解析不到 release(例如 ~/.dsh 这种没有启动器布局的安装)才归入 unknown,交给 DOM 形状仲裁。
- 0.1.7-rc.1 进入已核对范围,且不需要新的适配目录:把它与 0.1.7-alpha.2 逐包比对后,本插件匹配的每个锚点都还在原处,所以 0.1.7 目录只是在名字里多记了这个构建(v0-1-7-alpha-1-2-rc-1),并在 SUPPORTED_RELEASES 里多一行——rc.1 宿主的判定因此从 line 变成 exact。真正变了的是宿主读清单的方式:从 rc.1 起,profile 组装阶段会把插件声明的 @deepseek-ai/dsh peerDependencies 与运行版本比对,范围不匹配就在导入任何模块之前把该插件整行禁用,唯一放行办法是在插件管理器里授予「精确版本豁免」(写进 profile 自己的 compatibility.json)。engines.dsh 仍然只是声明,决定插件能不能加载的是 peer 列表——七个 @deepseek-ai/dsh- peer 因此都补上 || 0.1.7-rc.1。
- 确定宿主版本时不再依赖 :has():解析到 release 的宿主直接发本版本的定位分支,不带 :has() 门。这顺带修掉一个潜在问题:在不支持 :has() 的引擎上,原来的门控会让 0.1.5 / 0.1.6 的面板提升整条失效。解析不到 release 时仍归入 unknown,由 DOM 形状(:has() 双臂)仲裁,而不是猜一个版本。
- 头部浮层选择器单一来源:顶栏表面的选择器此前在 wallpaper.ts 里重复了三份,现在拼一次、由透明度与描边两条规则共用(描边那条刻意用不加 [role] 的原始标记,原因就近写明)。槽位键改由适配器提供,0.1.6-alpha.2 独有的 leading 槽位不再散落在底码里。
- 插件页的卡片列表加上块级底板:设置 → 插件 里每组插件的 ul 自身没有表面,设置界面淡出后整张表直接浮在壁纸上。现在每个分组列表拿到与对话输入框同一套处理——::before 底板撑出圆角磨砂块,底色由「设置界面」这一组驱动、模糊由「设置模糊」驱动(0.1.7 上该页是 centerCol 的子节点而不是设置对话框的后代,只跟着对话框的令牌重挂就会读错滑块)。这条规则只由确实带该页的版本适配器发出,没有插件管理页的宿主不会背上匹配不到的选择器。
- 修复展开右侧栏动画期间壁纸消失:AppFrame 自身的半透明底色此前是用内联 background: transparent 抹掉的,而宿主在侧栏开合动画里重写 style 时把它丢了——框架回到不透明,壁纸被盖住,其上每一层磨砂同时被压平(backdrop-filter 背后没有内容就无从模糊)。现在改为带 !important 的 class 规则,宿主的 style 写入碰不到它(同一个 frame 现场实测:不透明 rgb(200,207,218) → 挂上 class 后 rgba(0,0,0,0),去掉又回到不透明)。
- 修复启动时「对话卡片」套到设置页上:聊天视图尚未挂载时(例如开着设置面板启动),对话卡片的无标记回退会按几何挑「最大的可滚动元素」,于是设置里的页面被穿上卡片外壳、并挂上一层磨砂垫()覆盖整个窗口。现在回退只能细化已经存在的对话:中心列里找不到任何对话标记([data-chat-flow] / [data-conversation-scroll] / [data-composer-seat] / [data-conversation-composer-overlay])时不再靠几何猜;属于设置对话框表面的候选也一并拒绝。
- 修复分部位毛玻璃的覆盖区域:承载磨砂的元素那条类名规则漏写了开头的点(dab-part-blur{isolation:isolate}),所以该层从来没建立自己的层叠上下文,z-index:-1 的磨砂垫跑进了页面级的层叠上下文——模糊取样的范围和落位的位置都不再对应那个表面(现场实测宿主元素的 isolation 计算值为 auto)。补上点号后磨砂回到表面自身之内。这个缺陷早于 v0.3.0 就存在。
- 更正了几条对宿主的错误假设:原生右侧栏与 ctx.sidebarRightTabs 并非新宿主特性(0.1.5-rc.2 即已提供),dockkit 本身也存在得更早——只有 host / empty 两个属性才是 0.1.7 的标记。
- README:兼容性一节新增「版本适配隔离」条目,已知限制补充版本相关选择器的所在层;引言不再暗示官方右侧栏特性只出现在新宿主上。
v0.3.0(DSH 0.1.7 适配,兼容 0.1.5-rc.2 ~ 0.1.7-alpha.1)
- 适配 0.1.7 官方右侧栏:宿主重做了该面板——它现在是一个静止外框,滑动位移落在内部的 dockkit 子元素([data-dockkit-host="dock"] / [data-dockkit-empty])上,且面板本身不再绘制背景。插件随之调整:模糊改挂在真正滑动的子元素上,因此会跟随侧边栏一起移动而不是卡在原地;表面令牌也改为在面板实际渲染处重新作用域。原先无条件的 position:fixed 提升已移除——在 0.1.7 上它会让面板脱离动画轨道。
- 修复 0.1.6 右方侧边栏模糊失效:[data-dockkit-host] 从 0.1.7 才存在,所以基于子元素的选择器在 0.1.6 上匹配空集(0.1.6 的滑动打在面板自身)。现在新增一条用 :has() 门控的分支为面板包装元素加磨砂,且 pre-0.1.7 的提升只在真正需要处重新生效。
- 修复流光文字被描边压成纯色:-webkit-text-stroke 是继承属性,对话文本框的规则会一路继承到 background-clip: text 的活动状态文字上——「深度求索中」状态行(0.1.5/0.1.6)与轮次过程 / 流光行(0.1.7)会被糊成一块描边色。现在这些表面按 [role="status"]、[data-turn-process] 与 TextShimmer 标记被显式豁免。
- 新增「顶栏选项」表面:Agent Team 面板、后台任务列表、「用其它应用打开」/「会话日志」菜单与子代理血缘树拥有独立的透明度 + 模糊滑块,以及对应的描边分组。这些浮层被 portal 到 <body>(与头部断开),且 0.1.7 把「用其它应用打开」改为 portal、把会话行菜单改为动态槽位,因此插件新增一个运行时标记器,监听稳定的 conversation.session.header 槽位锚点来标记展开的浮层。豁免的确认对话框与会话行菜单用真实颜色字面量钉死——不再使用自引用的 var() 回退,那是 CSS 循环,会让表面彻底透明。
- 宿主版本探测:客户端上下文不暴露宿主版本(window.__DSH_BOOT__.version 是模块表格式标记,不是 release),因此改由 Node 半侧从启动器的磁盘布局解析出 release 与渠道(0.1.7-alpha 这一级),再经 read RPC 下发。功能开关据此判定;无法确定时回退到能力探测,而不是猜一个版本。
- 原生适配官方右侧栏:通过官方公开扩展点(ctx.sidebarRightTabs 注册页面类型 + sidebar.right.pane.tab 键控槽位挂载页面本体)在官方侧边栏的引导页挂上「主题」卡片,点开就是和设置面板一样的五页。未安装 dsh-better-sidebar 时生效;装了 better-sidebar 则让位给它自己的「主题」页(官方引导页卡片自动撤下,不重复占位)。注册走运行时动态等待:宿主没有右侧栏注册表 API 时静默跳过,旧宿主不受影响。
- 右方侧边栏栏位随环境改名:界面页的面板分组(panelOpacity / blurs.panel)未安装 better-sidebar 时显示为「右方侧边栏」,直接驱动官方右侧栏的表面令牌与毛玻璃模糊(0.1.5-rc.2 至 0.1.7 均适用,该行现在始终显示);安装了 better-sidebar 时改名为「bettersidebar」,语义不变。
- better-sidebar 存在性探测摘除了 [data-sidebar-right-panel] 标记——各代宿主中它都是官方右侧栏的稳定标记(随会话始终存在),继续计数会让「bettersidebar」判定永远为真。
- 兼容性声明现已覆盖 0.1.5-rc.2、0.1.5-rc.3、0.1.6-alpha.1、0.1.6-alpha.2、0.1.7-alpha.1;peerDependencies 放宽为同时覆盖各代客户端包版本;@deepseek-ai/dsh-home-paths 维持与 lockfile 一致的 ^0.1.0-rc.6(仅构建期使用,运行时由宿主注入自身版本)。
安装
方式一:从 GitHub 安装(推荐)
dsh plugin --profile web add github:lilcandi/dsh-any-background-plus
然后启动:
dsh web
插件会出现在设置面板的 “主题” 分类中。
方式二:npx(无需全局安装)
npx @deepseek-ai/dsh plugin --profile web add github:lilcandi/dsh-any-background-plus
npx @deepseek-ai/dsh web
方式三:本地构建(开发)
lib/ 目录已提交,安装后无需构建。修改 src/ 后重新构建:
git clone https://github.com/lilcandi/dsh-any-background-plus.git
cd dsh-any-background-plus
pnpm install
pnpm run bundle
pnpm dsh plugin --profile web add .
pnpm dsh web
兼容性
- dsh web 0.1.5-rc.2 ~ 0.2.1-alpha.2 — 闸门是 engines.dsh、七个受闸的 @deepseek-ai/dsh- peerDependencies 与 dsh.compatibility.dsh 里那条 semver 范围(>=0.1.5-rc.2 <0.3.0-0);从 0.1.7-rc.1 起宿主自己就会按 peer 列表拒绝加载,所以这些字段写了什么,就决定插件能不能跑,不是写给人看的说明。dsh.compatibility.dshReleases 继续保留「真的核对过哪些 release」的显式枚举——共十个,0.1.5-rc.2 到 0.2.1-alpha.2(0.1.6-alpha.2 与 0.1.7-alpha.1、0.1.7-alpha.2 已实测验证,0.1.7-rc.1 与 0.2.0-rc.1 以与前一版逐包比对核对)——因为在那里写范围就等于宣称覆盖了一堆没人验过的东西。宿主版本在运行时由 Node 半侧解析,依赖特定宿主版本渠道的功能(右侧栏面板模糊、官方侧栏「主题」卡片)只在对应宿主结构存在时启用,其余功能在整个范围内表现一致。
- 版本适配隔离:release 由 Node 半侧从进程自身那份 @deepseek-ai/dsh/package.json 读出(客户端上下文不暴露版本;ctx.profileContext.installAnchor 指不到时退回启动器的磁盘布局),经 read RPC 下发后只由前置适配层路由——src/host-compat/ 负责探测与渠道归类,src/client/host-compat/versions/ 下每个版本一个目录(v0-1-5-rc-2-3 / v0-1-6-alpha-1-2 / v0-1-7-alpha-1-2-rc-1 / unknown),各自描述该版本的面板力学与头部槽位键。底码只向适配层提问(引导页表面归谁、模糊挂在哪一层),不比较版本字符串。同一补丁行内的其它构建(0.1.6-alpha.4 之于核对基准 0.1.6-alpha.2)用该行的档位;整条补丁行都不在已核对范围内才收边取最近的一档并写日志。只有解析不到 release 时归入 unknown,回退到按 DOM 形状探测(:has() 双臂)而不是猜一个版本;适配新宿主 = 新增一个版本目录并在注册表登记。
- DSHA — DeepSeek Harness 安卓启动器(免 ROOT、免 Termux)。其包内 dsh 为 0.1.5-rc.2,落在兼容范围内;移动端界面由 dsh-web-mobile 提供。
- deepseek-harness-desktop — 支持
权限、副作用与边界
- 接入形态:官方 Profile Bundle——package.json 声明 dsh.bundle.patch: ./cordis.patch.yml(loader 插入层),仓库提交可直接使用的预构建运行时制品(lib/index.js、lib/invariant.js、lib/client.js),无安装脚本、无 postinstall、无 native 二进制、安装时不执行任何构建。
- 文件系统:服务端仅在 <dsh 主目录>/.dsh-any-background-data/ 内写入(配置 JSON、壁纸、轮换池、字体),不写入该目录之外的任何路径——唯一的例外是只读:启用文件夹轮换后,它会列出并读取你通过宿主自带的「选择文件夹」对话框选中的那个目录里的图片;该路径以绝对路径存进配置(界面里不能手输),且只读取、绝不修改。配置写入为原子写(临时文件 + rename)。这些文件落在真实磁盘上,不受 generation 恢复影响,也不会被其回滚——清除它们即彻底重置插件。
- 网络:仅在用户主动粘贴 http/https 图片网址并点击「应用」时发起一次出站请求下载该资源;除此之外无遥测、无外部服务调用。
- Shell / native:无。不使用 child_process、不加载 native 模块、不运行动态下载的二进制。文件夹选择复用宿主已挂载的 directoryPicker 服务(@deepseek-ai/dsh-host-directory-picker-auto,Windows 上由其 native 后端驱动系统对话框),本插件不新增任何 native 依赖。
- HTTP 面:仅在本机 dsh web 服务下注册 /dsh-any-background/{wallpaper,wallpaper-right,font}(GET/HEAD 流式服务)与对应 */upload(POST,上限 100 MB)及专用 RPC 通道 /dsh-any-background;无新增对外监听端口。
- 是否需要重启:首次安装后需(重新)启动 dsh web 加载客户端 bundle;此后的设置变更实时生效、自动落盘。更换插件版本后需重启以加载新的 lib/client.js。服务端那一半同样需要重启:它在宿主启动时被 import,进程内一直保留旧代码,且宿主没有 hmr 时无法热替换——症状是静默的,例如新增的轮换档位会被旧代码的兜底逻辑悄悄改回 daily。node verify-host.mjs 的最后一项会逐一探测这一点。
- 测试与验证:pnpm run typecheck(tsc 全量类型检查)、pnpm run bundle(tsdown 产出 lib/)与 pnpm run selfcheck(selfcheck.mjs,无测试框架依赖的单个 node 脚本:既覆盖纯函数边界,也用桩宿主驱动真实的 RPC 处理器验证文件夹轮换的选目录 / 推进 / 清除流程)。node verify-host.mjs 则针对正在运行的实例做同类验收:确认兼容性闸门放行、启动文档确实列出并下发本插件的客户端 bundle、RPC 通道可应答,并真实执行一次轮换——壁纸槽位换成配置目录里的另一张图。它会实际轮换一次,故为手动触发的可选项;其余接口行为以手动验证为准。
- 已知限制:依赖宿主 DOM 结构的稳定标记(如 [data-sidebar-right-panel]、[data-dsh-bottom-panel])与 CSS 令牌名,宿主大版本重构样式层时选择器可能失效(表现为相关滑块不再作用于对应表面,不影响稳定性);版本相关的那部分选择器集中在对应的版本目录里,宿主改版通常只需改那一份;-webkit-text-stroke 在部分单行省略号容器边缘约有 1px 裁切。
Star History
许可
MIT预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
lilcandi/dsh-any-background-plus
一个 DeepSeek Harness 外观插件:自定义主题色、背景壁纸图片 /…
相关插件 · 市场 / 管理
插件精选列表awesome-dsh-plugin/awesome-dsh-plugin
DeepSeek Harness(dsh)插件精选列表
★ 22,225未汉化DSH
73.6
新词雷达zhuyansen/new-term-radar
new-term-radar新词雷达
★ 2,049已汉化MCP
72.8
插件生态导航0xsline/awesome-deepseek-harness
安装 贡献指南 DeepSeek Docs 公开插件目录 Issues
★ 1,188已汉化DSH
72.6
技能注册中心iflytek/skillhub
企业级开源智能体技能注册中心——发布、发现并管理组织内可复用的技能包。
★ 5,370已汉化DSH
71.9
插件市场目录imsai-sh/awesome-deepseek-harness-plugins
面向 DeepSeek Harnessdsh生态的社区插件目录,共收录 13941 个插件含 PR 收录与…
★ 355已汉化DSH
71.8
插件精选清单Anil-matcha/awesome-dsh-plugin
DeepSeek Harnessdsh精选指南——DeepSeek…
★ 1,013已汉化DSH
71.7
这是你的仓库?认领条目