packages/dsh-task-board
zhu1090093659/dsh-web侧边栏多列任务看板:卡片交给真实 DSH 智能体会话执行,支持 cron 定时(Host 侧到点执行,关浏览器也生效)。
打开新对话、或每次打开你钉住的那个会话时,铺满窗口播放一段片头动画。插件自带四段片头,直接内嵌在代码里(不带独立的视频文件);界面里的片库可切换、可预览,也能随时换成你自己的片子;设置旁一键钉住会话。
给 DSH 加一段开机动画:每次打开 DSH 播一次,视频铺满整个窗口。
English: README.en.md
dsh plugin --profile web add github:NativeDog1/dsh-boot-animation仓库里已经提交了构建产物lib/,也没有prepare生命周期脚本,
所以这条命令不编译任何东西,不会触发 pnpm 的 allowBuilds 构建授权。(等包发布到 npm 之后,也可以写成 dsh plugin --profile web add dsh-boot-animation。)DSH 版本线 · 状态 0.1.5-rc.x / 0.1.7-rc.x · ✅ 支持(桌面版旧内核,以及 npm i -g @deepseek-ai/dsh 那条线) 0.2.0-rc.x · ✅ 支持(桌面版新内核,以及 @next 那条线)
**本插件刻意不声明任何 @deepseek-ai/dsh-* 的 peerDependencies。 host 半边只用 `ctx.webServer`,客户端半边只通过动态注入取 `slots` / `uiSession`,两处都不静态 依赖某个宿主包。这样做的直接好处是:它永远不会被 dsh 的版本兼容闸门跳过 —— 0.2.0 起那道闸门会把 `peerDependencies` 对不上的插件整包跳过**,现象是"装上了、 但重启后什么也没发生",而且不报错(市场自己的兼容守卫也会拦)。其它插件要靠 ^0.1.5-rc.3 || ^0.2.0-rc.1 这种 || 列表来适配,本插件不需要,也不会因为漏写 某条版本线而被跳过。
代价是宿主形状变了得自己容忍,这部分由测试守着:verify-blank(13 种宿主形状)、 verify-session-id、verify-client-boot(11 项 boot 安全)。例如"这是不是全新对话" 在 0.2.0 从 binding 上的 blankBit 挪到了 session.getSnapshot().blank —— 两种都读, 并且订阅那个 face 本身而不是采样一次。
装完必须重启一次 DSH 服务才生效(bundle 层是在启动时装配的):
# 停掉当前的 dsh web,然后
dsh webDSH 的客户端 bundle 响应带 cache-control: max-age=31536000, immutable, 而 URL 上的 rev 是进程 nonce、不会随内容变化。所以浏览器会一直用第一次抓到的副本。
装好或升级后请在新窗口里按 Ctrl+Shift+R(硬刷新)。普通 F5 不够。
片库面板(页脚 🎛)里有一行明确写着:
什么时候播: [ 🚀 每次启动 DSH 时播放一次:开 / 关(只在新建会话时播) ]开关 · 行为 开(默认) · 每次启动 DSH 应用时播一次 —— 在启动后你进入的那个对话里播。同一轮使用中切会话、新建会话都不重复;下次打开 DSH 再播一次 关 · 回到原来的行为:只在新建会话时播一次(一个会话记一次,之后不再播)
sessionStorage(dsh-boot-animation:boot),浏览器把它限定在一个标签页里、关掉标签页就清掉,所以开关打开时的「一次」= 一次应用启动,而不是「一个会话」
$DSH_HOME/boot-animation/selection.json 的 playOnAppStart),不存 localStorage —— localStorage 按来源(host:port)隔离,DSH 换个端口设置就丢了
0.4.1 及之前没有这个开关,固定是「每个会话只播一次」—— 于是同一个会话里片头只播一次、
之后永远不再出现。这就是「开了一次之后就再也没有见过」「是个一次性的」的成因。
0.4.2 把规则交给你选,默认值是新的「每次启动播一次」,所以默认行为变了:
如果你要的是老行为,把开关关掉即可。
直接按 F5 刷新页面会再播一次(开关打开时):sessionStorage 在同一个标签页里能扛过刷新。
如果你连着刷新好几次都看得到,那不是 bug。
之后每次进入这个会话都会播一遍 —— 切走再切回来、刷新页面,都会重播。 无论上面那个开关是开还是关,都一样。
再点一下图标取消。
注意:如果刚启动时你的活动主面板不是「对话」(比如停在某个插件的面板上),
当前会话还不存在,图钉是禁用状态。先打开一个对话即可。
上面那个 🎞 图钉管的是「什么时候播」;如果你要管「这个会话播哪一段」, 用片库里的 「仅本会话」:
那一行会变成蓝底并挂上「本会话」徽章,面板顶部也会写明「本会话固定播放:xxx」, 旁边有 「取消(回到全局)」。之后只有这个会话播这一段 —— 其它会话、以及你在别处选的那一段,都不受影响。 「选它」改的是全局,「仅本会话」改的是这一个会话,两者互不覆盖。
优先级:会话覆盖 → 随机 → 全局选择 → 环境变量 → `intro.mp4` → `videos/` → 内嵌内置片。 会话覆盖连「随机播放」也压得住 —— 把一个会话钉住之后又被随手塞一段随机片, 那不是"钉住"的意思;但显式请求随机的调用(mode=random)仍然不被覆盖。
指的那一段被删掉或移走时,这个会话回落到全局而不是黑屏,并在 diagnostics 里
记一条 conversation-override-stale;文件回来了这条钉住自动生效(不会因为一次失联就被清掉)。
进来是「显影」,出去是「虚化淡出」,都不是硬切:
阶段 · 视觉 · 时长 进入 · 视频从 opacity 0 / blur(14px) / scale(1.04) 显影到清晰 · 340ms 退场 · 视频 opacity → 0 + blur(16px) + 轻微放大;同时整层底色变透明,backdrop 模糊从 18px 衰减到 0 · 460ms(浮层 500ms 后才卸载)
退场的模糊在视频背后,所以应用是「先糊后清」地浮出来,而不是从硬边后弹出。 退场期间底层立刻可点(pointer-events:none),不需要等动画放完。 系统开了「减少动态效果」(prefers-reduced-motion)时,这两段动画直接关掉。
两个时长(460ms 与 HANDOFF_MS = 500)写在不同文件里,必须保持前者更短 —— 动画被半路切断比没有动画更难看,npm run verify:handoff 专门断言这一条。
插件现在是一个片库,不是单个槽位:它会把所有能找到的视频都列出来,你选一个,选择会被记住。
装完不用加任何东西,片库里就已经有四段可选:
片库里的名字 · 来源 · 大小 DeepSeek 品牌片头 · 内嵌 lib/clips.data.js · 1.2 MB DeepSeek 赛博朋克片头 · 内嵌 lib/clips.data.js · 1.8 MB DeepSeek 数字角色苏醒 · 内嵌 lib/clips.data.js · 2.5 MB DeepSeek 启动问题 · 内嵌 lib/clips.data.js · 3.2 MB
这些片段没有落盘的 mp4 文件 —— 它们以 base64 存在 lib/clips.data.js 里,host 在 第一次被请求时才 import(约 11.5 MB 的模块,如果在启动时解析,每次开 DSH 都要白付这个代价)。 这样做的意义是:不会再有 files 字段漏写、安装副本过期、或者随包发出一个没做 faststart 的容器这些事。media/*.mp4 只是 npm run embed-clips 的输入,不随包发布。
四段都是 faststart 过的(moov 在文件头),可以边下边播;生成脚本会拒绝任何 moov 不在前面的输入。这很重要:索引表在文件末尾的 mp4 要整段下载完才出画面, 叠加客户端 25 秒看门狗,表现就是「片头全黑」。
体积:npm 包 9.1 MB(tarball)/ 解包 12.2 MB。四段都做过 CRF 20 重编码 + faststart 重排,相比各自的原片省下 36%–71%(合计 20.4 MB → 8.7 MB),画质指标 SSIM 0.988–0.996、 PSNR 44–48 dB —— 这是「肉眼看不出差别」的区间。原片另存于仓库外 ~/dsh-dev/_clip-masters/。
想换成自己的片子:把 mp4 放进 media/,改 scripts/embed-clips.mjs 里的清单, 跑 npm run embed-clips。(临时试片不用这么麻烦 —— 见下面的「最省事的方式」。)
~/.dsh/boot-animation/videos/选中的那段会在下一次播放片头时登场:下次启动 DSH、以及你钉住的会话。
Windows 上就是 C:\Users\.dsh\boot-animation\videos具体路径以片库面板底部显示的那一行为准。
你不需要为了"干净"删任何东西:同一个视频存在多份副本时,片库只列一条
(按 大小+mtime 判定同一份内容),并标注「合并 N 份重复」。你的文件一直留在
原处,只是不重复显示。这解决的是历史上一个真实的困惑:用户自己也放了一份
intro.mp4,于是同一段片子在面板上出现两次、挂两个不同徽章,看起来像"插件里没有这一段"。
元素 · 作用 ✓ 标记 · 当前生效的那一条(全局选择) 来源徽章 · 你自己加的 / 插件自带 / 内置原始 / 环境变量 文件大小 · 帮你确认换对了没有 ▶ 预览 · 立刻播放这一行,不改你的任何选择 选它 · 把它设为全局片头(以后开片头都播它) 仅本会话 · 只让当前这个会话播它,不动全局选择(见上) 本会话 徽章 · 这一行是这个会话固定播放的那段 面板顶部的本会话条 · 本会话当前固定播放哪一段,带「取消(回到全局)」 刷新 · 刚往文件夹里丢完文件,点它重新扫描 原片源 徽章 · 历史上那个 intro.mp4 落点,仍然优先 ⚠ 未优化 徽章 · 该文件的索引表 moov 在末尾,建议重排(见下) 铺满屏幕 / 完整显示 · 播放时怎么贴合窗口,见下
片头动画的触发故意很窄:
sessionStorage)所以在同一个会话里切来切去,片子本来就不会重播 —— 这很容易被误认为 "我换了片子但另一个视频不出现"。点片库里的 ▶ 预览当前 可以立刻播放选中的那段, 确认换对了没有。
覆盖层铺满整个窗口,但窗口的长宽比几乎不会是视频的长宽比——浏览器有标题栏和 工具栏,可视区通常比 16:9 更宽。这时: