dsh-web
zhu1090093659
DeepSeek Harness (DSH) Web 插件聚合生态 · 万物皆插件,通过创意工坊分发||DeepSeek Harness (DSH) Web Plugin Aggregation Ecosystem · Everything is a plugin, distributed via the Creative Workshop
PROJECT TOPICS
INSTALL REFERENCE
dsh plugin --profile web add github:yanzwzz/dsh-whale-girl-pet
该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。
PROJECT README
一只住在 DeepSeek Harness Web 界面右下角的「Q 版蓝发鲸鱼女仆(DeepSeek 娘)」桌宠。 她会在你工作时敲键盘、摸鱼时偷懒、任务完成时庆祝,还能报今日 Token 消耗与花费、查天气、喂她吃「TOKEN 小鱼干」。
| 场景 | 动画 |
|---|---|
| 🟢 开工 | 「开始工作」:变出悬浮电脑桌 → 站姿开敲 |
| 🔄 工作中轮播 | 「认真工作 / 工作摸鱼 / 工作思考 / 摸鱼被抓」每 10.5s 随机切换 |
| 🎉 收工 | 「工作结束」:伸懒腰 → 比耶庆祝 |
| 😤 忙时点击 | 「工作被打扰」:惊到 → 嫌弃 → 继续干 |
| ⏰ 长任务超时 | 「长时间工作看表」:看表叹气(阈值可调) |
每轮任务结束自动弹出多行排版,花费按 缓存命中 / 缓存未命中 / 输出 三桶分别列出:
任务完成啦!
用时 2分35秒
消耗 1.2M tokens
花费 ≈¥3.21
· 缓存命中 ≈¥0.28
· 缓存未命中 ≈¥0.02
· 输出 ≈¥2.91
lib/usage.js 的 CN_STATUTORY_HOLIDAYS,按国务院通知逐年扩表)prompt_cache_miss_tokens)宠物侧边按钮组的第四枚按钮,点开是一块手写内联 SVG 看板(零图表依赖),回答「钱花在哪段时间」:
两种视图 × 两种指标
| 今日分时 | 近 7 天 | |
|---|---|---|
| 横轴 | 今天已过去的每个北京小时 | 最近 N 个北京日(N 可在设置面板调 1–30) |
| 竖轴 | 该小时花费 / token | 该天花费 / token |
| 切换 | 「花费 ↔ token」一键切换,右侧「⟳」手动刷新 | 同左 |
图怎么读
汇总格(5 格)
近 24 小时 · 今日 · 均值 · 输入 · 输出
cacheRead / (input + cacheWrite + cacheRead),直接回答"这段时间省不省"面板随手放
视口 − 24 自动收窄,不会顶到屏幕边)localStorage,刷新/重开都记得;双击头部恢复默认尺寸与居中数据从哪来
ctx.on('session/event') 增量折叠,每条事件 O(1),不轮询、不写盘sessionPersistence 补扫已落盘会话(含重启前的今天早上),面板底部显示"已补扫 N 个会话 · M 条事件"GET /api/whale-pet/usage(可选 ?hours=1..24&days=1..30),响应 no-store,约 10 KB健壮性:弹层容器经 portalContainer() 校验(非真实 DOM 元素时退回内联渲染,避开 React #200),整个看板包在错误围栏里——看板自身出错只会显示错误文本,绝不会把桌宠打挂。
实现拆成三块:
lib/usage-ledger.js(分时段账本,纯逻辑)、宿主路由/api/whale-pet/usage、浏览器侧client.js内的 SVG 图表与布局 hook。
余额 + 今日消耗 tokens + 今日花费(峰谷分开计费,并按命中/未命中/输出三桶拆分),调用官方余额接口。顺手播一段「翻钱包」动画,结果汇入头顶气泡。
今日花费 = 内部统计,含子代理会话与重启前的历史:数字取自分时段账本(实时折叠所有会话 + 启动/按需补扫已落盘会话),而不是"当前在线会话"。所以主会话派发出去的子代理(subagent)开销、以及进程重启前今天已经花掉的部分,都算在内。
与官方 token 用量 pill 同排显示本会话累计费用,点击展开明细弹层:缓存命中 / 缓存未命中 / 输出三桶金额、高峰与空闲各自累计、计价调用数,并带实时「谷 / 峰」徽标。金额与任务完成气泡、余额按钮共用同一套价目与计费口径(lib/usage.js 为唯一内核,whalePetCost 投影供浏览器读取)。
金额含子会话:
whalePetCost投影只折叠本条会话自己的日志,而子代理是独立会话,所以光靠投影会漏掉它们。宿主半侧按会话树(sessionPersistence的parentSession血统)汇总后代会话的开销,经GET /api/whale-pet/subtree-cost提供给前端叠加;明细弹层里单列一行「子会话」。
DSH 0.1.6-alpha.2 起,官方把输入框下方的统计区改成了横向 flex 行(官方 stats pill + 上下文占用计 + 本费用 pill 同排,
gap:12px)。本插件的费用条目已按新契约声明为一个普通行内 flex 项,间距与垂直居中交给官方 dock;旧版 DSH 下它会退化成自己居中一行(不会与官方行重叠)。
就在官方「用量 X tok」旁边多一枚「费用 ≈¥x.xx」,点开是这一轮的缓存命中 / 缓存未命中 / 输出三桶金额与高峰 / 空闲拆分。数据来自同一个 whalePetCost 投影的 byTurn,与会话累计同源;本轮派发出去的子代理开销按"子会话创建时刻落在哪一轮"归到该轮(明细里同样单列「子会话」)。
主打明日预报(今日天气抬头就能看见 😄),支持中文城市名 / 自动定位,WWO 天气码本地中文映射(wttr.in 返回的是 WWO 三位码:113=晴、116=局部多云、122=阴)。
宿主半侧用 Node 的
fetch直接请求 wttr.in,不经过 shell:Windows / Linux / macOS 行为一致,也不需要为「网络」申请沙箱策略(0.3.6 起)。
吃「TOKEN」压字小鱼干(30 秒冷却),点击/双击/拖拽各有专属动画。
空闲 5 分钟自动入睡三连(进入睡眠 → 持续睡觉循环 → 被叫醒)。
番茄钟提醒(间隔可调)· 深夜关怀 · 随机小剧场 · 漫游开关 · 按钮位置(左/右) · 看板历史补扫开关 · 看板窗口天数 · 长任务阈值 · 天气城市,全部即时生效、重启不丢。
全部透明 WebM(VP9 alpha),双缓冲交叉淡入切换零空白,落地对齐统一,支持 prefers-reduced-motion。
# 从 npm 市场安装(推荐)
dsh plugin --profile web add dsh-whale-girl-pet
# 或从本地 tarball 安装
dsh plugin --profile web add dsh-whale-girl-pet-0.3.0.tgz
重启 dsh web,刷新浏览器页面,桌宠出现在右下角。
⚠️ 改动客户端 bundle 后必须重启
dsh web:DSH 在启动时就把插件的浏览器半侧载入内存,不是每次从磁盘读。只刷新页面看不到新代码。 宿主半侧(路由、计费、lib/*.js)同理,任何lib/下的改动都需要重启进程。
🧩 兼容性(0.3.4):本版本针对 DSH 0.1.7-alpha.1 验证(隔离实例实测:
apply()正常激活、/pet/*与全部/api/whale-pet/*路由 200、真实浏览器里桌宠渲染 + 两枚费用 pill 正常 + 工作/停止动画联动正常 + 控制台零报错)。本次适配的是它同时改掉的三处服务契约。✅ 追加验证:DSH 0.2.0-rc.1 无需改代码。0.1.7-alpha.1 → 0.2.0-rc.1 之间,本插件真正调用的服务(
dsh-settings、dsh-session-projection、dsh-jobs、dsh-host-webserver、dsh-home-paths、dsh-session-persistence、dsh-shell、dsh-sandbox、client-locale、client-ui-slots)源码零改动(只有 README.i18n / package.json 变更);agent/status、session/event事件名与/plugins/<entryId>/client.js客户端 bundle 契约均保留;mode/headless/executablePath等配置字段不变。全套自检(32 项)在 0.2.0-rc.1 检出上通过,故本次只把peerDependencies的 DSH 范围对齐到^0.2.0-rc.1。⚠️ 0.1.7 的三处破坏性变更(0.3.2 及更早在 0.1.7 上会整体失效):
dsh-settings:SettingsProvider→SettingsForms,删掉了ctx.settings.register()/get();dsh-jobs:删掉了ctx.jobs.onJobDone(),改成ctx.jobs.events.subscribe(filter, listener),结算经settled事件(带job投影与cause);dsh-shell:run(spec)→execute(spec),且execute()返回的是进程句柄,完整前台结果要再await handle.result()。老代码在
apply()里抛TypeError→ DSH 判定该 entry "did not activate" → 表现就是桌宠整个消失(连浏览器半侧都不挂)。0.3.3 逐一适配(settings 走 profile 表单模型 +.volatile()+ctx.fiber.config解包;jobs 走事件流并保留 old-API 回退;shell 兼容execute()/run()两条路径),并且把每段可选功能的装配各自兜住——以后再有单个 API 漂移,只会丢掉那一个功能(日志一条 warn),不会让桌宠从页面上消失。package.json的peerDependencies随 DSH 的 alpha 线走(^0.2.0-rc.1):npm/pnpm 的 semver 规则要求 peer 范围里必须点名同 patch 的预发布版本才能算满足,所以每次 DSH 换 alpha 线这条范围也要跟着更新。DSH ≥0.2.0 起这条范围不只是警告:运行时会在启动前做兼容性预检,范围不满足则该 bundle 被拒绝加载(skipping profile bundle,桌宠完全不出现),需在 profile 目录授权 exact-version 豁免或把范围升到当前版本线。 升级后如要跑一遍兼容性自检(只有源码工作区带scripts/,npm 包里不含):cd D:\deepseek-harness && node --import tsx/esm "<源码工作区>\dsh-whale-pet\scripts\verify-dsh-0.1.7.mjs"(32 项检查:settings API 形状、apply()激活、inject 全覆盖、零功能跳过、7 条路由、子会话计费端到端、volatile 解包、/state权威 running、jobs 事件流、shell execute/result)。
打开 DSH 设置 →「桌宠配置」面板即可调整全部选项(即时生效并在下次写回时落进 profile 补丁):
DSH ≥0.1.7 的配置存放位置变了:0.1.6 及更早写
$DSH_HOME/settings.yaml的whale-pet:段;0.1.7 起改为 profile 补丁($DSH_HOME/profiles/<profile>/cordis.patch.yml)里该插件条目的config。升级后旧settings.yaml会被 DSH 一次性改名成settings.yaml.imported,第三方插件的段不会被自动导入,需要手工搬:# ~/.dsh/profiles/web/cordis.patch.yml - id: pet config: city: 济南 roam: false
| 配置 | 说明 | 默认 |
|---|---|---|
| 番茄钟提醒 | 每 N 分钟提醒休息 | 开 / 25 分钟 |
| 深夜关怀 | 23:00-05:00 每 20 分钟提醒早睡 | 开 |
| 随机小剧场 | 随机冒 DS 梗台词 | 开 |
| 漫游走动 | 关闭后桌宠不乱跑 | 开 |
| 按钮位置 | ☁️💰🍪📊 放宠物左侧/右侧 | 左侧 |
| 看板历史补扫 | 关掉则只统计本次运行的用量 | 开 |
| 看板窗口天数 | 日趋势保留并展示的天数 | 7 天 |
| 长任务提醒阈值 | 工作超过 N 分钟提醒 | 10 分钟 |
| 天气城市 | 留空 = 自动定位 | 空 |
| 费用显示:会话费用 pill | 输入框下方那条「费用 ≈¥x.xx」(与 dsh-cost-meter 重合时可关) | 开 |
| 费用显示:本轮费用 pill | 每条回复动作行里的本轮费用 | 开 |
| 费用显示:任务完成气泡花费 | 气泡里的花费与三桶(关掉仍保留「用时/消耗」) | 开 |
| 费用显示:💰 余额按钮 | 余额 + 今日花费气泡(与 cost-meter 的余额/今日花费重合) | 开 |
| 费用显示:📊 分时段花费看板 | 小时/日趋势看板 | 开 |
与 dsh-cost-meter 共存:两个插件在「本会话费用 / 今日花费 / 余额 / 峰谷」上有功能重合(它还有预算、Coding Plan 额度、价目同步等我们不做的东西)。所以 0.3.6 起把我们这边每个显示费用的位置都做成了独立开关(上表最后五行,默认全开)。关掉只影响显示 —— 费用投影、分时段账本与所有
/api/whale-pet/*路由照常统计,随时开回来即可。另外我们的会话投影键已改为whalePetCost,不会再和它的costUsage撞键(issue #1)。
电脑上印着鲸鱼剪影 logo,工作链动画同一会话生成保证一致。
(N) 计数(N = 你离开期间整批结束的次数),回到该标签页即自动清掉。DSH 的 DocumentTitle 会在会话标题/面板变化时整体重写 document.title,所以这里挂 MutationObserver 把外部写入记成新基线、再把计数补回去;favicon 是把 link[rel~="icon"] 的 href 换成 canvas 合成的角标图,原 href 先存下来供还原(DSH 有明暗两个 SVG,逐个替换)。done 事件带的 durSec / tokens / costCny / costHitCny / costMissCny / costOutCny(与气泡共用同一次 computeTaskUsage),不解析气泡里的中文文本;花费明细跟随 costInBubble 开关。若那一拍只有后台任务/子代理这类没有数值字段的事件,回前台时退回显示它的原文气泡(而不是"用时 0秒")。pointerdown / keydown / touchstart 时创建并 resume() 一次 AudioContext,拿不到就静默跳过。音量按 0–100 走(默认 60,0 等于静音);每个音的峰值给到 0.75–0.95 并加了 40% 时长的保持段,最后整体过一次 DynamicsCompressor 兜住四音叠加 —— 所以拉到 100 是"够响"而不是原来那种轻响档。响铃时机固定为「全部完成后」(不给选项):判定依据是宿主每次轮询现算的 running,所以子代理、后台任务陆续结束时不响,只在"最后一个任务结束"那一拍响一声。支持自定义提示音:设置面板里可直接选一个本地音频(≤1MB),客户端 decodeAudioData 解码后与合成音共用同一个 AudioContext 与压缩器输出;文件存 localStorage(只对本机浏览器生效),格式解不出来会回滚并回退到合成音,不会出现"设了却没声音"。done 的标签页"负责,而宿主的事件队列是读走即清空的(/api/whale-pet/state 里 queue.splice(0, queue.length)),同一条完成事件只会有一个标签页收到。white-space: nowrap 不再折行。test/notify.test.mjs(15 项),含一条强不变式——Config 里每个可写字段都必须能在设置面板里找到(size / position 由 DSH 原生表单负责,豁免),避免"加了配置却忘了给开关";另有"合成提示音必须是四个音(成功逐音升、失败逐音降)""峰值 ≥0.7 且过压缩器""宽控件必须走堆叠行""汇总不得再统计任务个数、必须与平时气泡同形"等断言。test/host-routes.test.mjs 的接线断言随"computeTaskUsage 只算一次、同时喂给气泡与结构化摘要"而更新。agent/status 对每一个 agent 都发(payload 是 { agent, status }),而宿主没过滤 payload.agent —— 子代理收工同样发 idle,于是把任务窗口清零、还顺手用掉 4 秒冷却;主任务真正收尾时算不出用量,只剩兜底文案「这一轮任务已经搞定啦~」("回来汇总"也因此没有钱)。现在批次判定改挂 agent 注册表的聚合事实 anyAgentRunning(ctx):第一个 agent 开始跑记开局、最后一个收工才算这一批结束。于是子代理不再能提前结束这一批,边沿事件漏发(宿主在任务中途重启、插件晚挂载)也不再影响结果,而且它与客户端提示闸门用的是同一次响应里的 running,两边永远同源。test/host-routes.test.mjs 新增"中途有子代理收工,这一批结束时仍必须算出用量"用例,并把端到端用例改为驱动 agents 桩;test/notify.test.mjs 补断言"批次判定不得再依赖 agent/status 边沿"。--dsh-pet-flip:-1、shadow 内的 video 读它",粒度是整个朝向:只要翻过面,之后播的每个动画都会被镜像 —— 举牌类动画的字会变成反字;而且翻面那一刻正在淡出的旧帧会被一起翻转(两个缓冲共用同一个变量)。.is-flipped 类:shouldMirror(name, dir) 同时要求"朝右"且"在白名单 MIRRORED_WHEN_RIGHT 里"(目前只含 ['螃蟹走路'],要放行别的动画只需往数组里加名字)。el.dataset.anim 记下每个缓冲当前播的是谁,切动画时(switchTo)与朝向变化时各重算一次,所以白名单外的动画、以及淡出中的旧帧都不会被连带翻转。facing)本身仍然只在「东张西望」播完时翻转、仍然决定漫游方向;向左走不镜像(人物原生朝向就是左侧),只镜像"往右走的螃蟹走路"。原地漂浮踏步 不在白名单里,所以它朝右走时不镜像,走路姿态与位移方向不一定一致;要一起镜像就往 MIRRORED_WHEN_RIGHT 里加它。test/client-bundle.test.mjs 的不变式随之改写:禁止回退到"祖先变量统一镜像"、白名单必须只含螃蟹走路、shouldMirror 两个条件缺一不可、is-flipped 只允许两处落地点且都必须经过 shouldMirror。npm stage publish 推入 npm 暂存区,但「批准」这一步在 registry 侧始终返回 404(账号未开启 2FA,npm stage approve 无法完成在场校验),而暂存记录又会挡住同版本直发(409 Cannot publish over previously staged version "0.3.6")。因此改以 0.3.7 直发 —— 与 0.3.6 没有任何代码差异;GitHub 上 0.3.6 的 release 与 tgz 照旧可用。.dsh-pet-video 少了 touch-action:none:拖拽走 Pointer Events(pointerdown 只记起点 + setPointerCapture,pointermove 超过 5px 才算拖拽,pointerup 收尾),触屏上浏览器会先把这一按当成平移/缩放手势并随即发出 pointercancel,而 onPointerCancel 正好接到收尾逻辑 —— 拖拽在起步前就被结束。桌面端没有这层手势拦截,所以只在触摸屏复现。看板标题与缩放手柄一直是这么写的,只有宠物本体漏了。新增 test/client-bundle.test.mjs 不变式:视频必须含 touch-action:none、拖拽必须仍走四个 Pointer Events 处理器、setPointerCapture 仍在,并顺带锁住看板两处同类写法。ctx.get('shell') 执行,而 DSH 在非 Windows 平台上的 shell 服务是 bash -c,第一行 [Console]::OutputEncoding 就报「未找到命令」。现在改成宿主半侧用 Node 的 fetch 直连:宿主本身就跑在 Node 里(engines 要求 ≥22.19),跨平台行为一致,失败时能带回真实的 HTTP 状态,也不再需要为「网络」放宽沙箱策略(runShell() / resolvePolicy() 随之删除)。lib/weather.js(WWO/WMO 码表 + 明日天气选取)、lib/balance.js。返回给浏览器半侧的字段与旧实现逐字一致,客户端无需改动。weatherCode 是 WWO 码(113=晴、116=局部多云、122=阴),而旧码表是 WMO 的,WMO 表里 >= 95 → ⛈️/雷雨 会把所有三位码吞掉 —— 表现就是天气图标永远是 ⛈️、描述永远是「雷雨」(issue #2 报告里真机复测的输出 "icon":"⛈️","tomorrowDesc":"雷雨" 正是这个 bug 的指纹)。现在 >= 100 走 WWO_TABLE(标准 48 码 + 实测出现的 149=霾),< 100 仍按 WMO 表兼容。test/weather.test.mjs 与 test/balance.test.mjs;scripts/verify-dsh-0.1.7.mjs 第 7 段由「必须调到 shell.execute」改为「一次都不能调到 shell」,并断言失败信息里不再出现 bash / PowerShell 语法错误特征。MutationObserver 扫 document,把「正在播放且时长 ≥ 8s」的 <video>/<audio> 注册为 MPRIS 播放器;宠物动画是循环播放的长视频,于是 playerctl 里出现的是它 —— xesam:title 待机呼吸休闲、xesam:url …/pet/thumb/待机呼吸休闲.webm —— 而 MPRIS 服务名全局唯一,网易云这类播放器就再也注册不上。<video> 现在建在 .dsh-pet-stage 的 shadow root(mode: 'closed')里:shadow 内的节点对 document 级扫描不可见(观察器不跨边界、querySelectorAll 也不进 shadow),媒体事件又都不是 composed 事件,MPRIS 因此看不到宠物。用 closed 让 element.shadowRoot 也返回 null(连「shadow 感知」的扫描也拿不到),引用由模块级 WeakMap 按舞台元素持有 —— shadow root 一旦建立无法移除,而 StrictMode 会把 effect 跑两遍,所以复用同一份并 replaceChildren 覆盖内部节点。.dsh-pet-root[data-facing="right"] .dsh-pet-video 这类跨边界后代选择器失效,而 :host-context() 在 Firefox/Safari 从未实现(且已移出规范)。现在由祖先设 --dsh-pet-flip:-1,shadow 内写 transform:scaleX(var(--dsh-pet-flip,1))(自定义属性能穿透 shadow 继承)。.dsh-pet-stage)—— props 永远匹配不上。现在两个 video 手工创建、原生 addEventListener,事件转发到每帧刷新的 handlersRef(避免绑在首次渲染的过期闭包上;setPointerCapture 的 currentTarget 仍是 video 本身)。document.head,也注入 shadow,避免两处手抄漂移。播放逻辑(switchTo 的 src/load()/classList/play()/pause()/onended)操作的一直是元素引用,未做任何改动。test/client-bundle.test.mjs 新增静态不变式:必须 attachShadow + mode:'closed'、不得再有 h('video'、必须有 replaceChildren/WeakMap、不得出现 :host-context、必须有 --dsh-pet-flip 桥接、事件必须走 handlersRef,并断言 shadow 的 effect 声明在 switchTo 的 effect 之前(顺序错了首次挂载会空白)。sessionProjections 把键当全局命名空间:同一个键被两个不同 stateVersion 注册时直接抛错(is already registered at stateVersion 9; refusing to share it with stateVersion 2)。两个插件都用 costUsage —— 0.2.0 时代的表现是整条 entry 装配失败、桌宠直接消失;0.3.3 起每段功能都被 safe() 兜住,桌宠不再消失,但我们的费用 pill 会去读别人的 costUsage 投影(形状不同 → 数字错或报错)。现在投影键改为带前缀的 whalePetCost(lib/cost-projection.js),浏览器半侧两处 useProjection() 同步改名。costPillSession —— 输入框下方的「会话费用」pill(composer dock)costPillTurn —— 每条回复动作行的「本轮费用」pillcostInBubble —— 任务完成气泡里的花费与三桶(关掉仍保留「用时 / 消耗」,由 taskSummaryLines(…, { withCost: false }) 实现)costBalanceButton —— 💰 按钮(余额 + 今日花费气泡);关掉后宿主也不再计算今日用量,响应只留余额costDashboard —— 📊 分时段花费看板(按钮与弹层一起关)/api/whale-pet/* 路由照常统计,随时开回来。publishPetSettings / usePetSettings):两个费用 pill 是注册在别的槽位上的独立组件,拿不到桌宠内部的 settingsRef;所以 apply() 先用客户端 config 同步打底(避免"已关但先闪一下")、再拉一次 /api/whale-pet/settings 校准,桌宠每次轮询再把最新设置发布出去 —— volatile 设置不会重挂插件,只能这样推送。taskSummaryLines 的 withCost 分支、五个 schema 开关的默认值、costBalanceButton 关闭后响应不含 usage,以及客户端「每个显示位置都检查了自己的开关 / 提前 return 排在所有 hook 之后 / 设置面板五个标签齐备」的不变式。test/host-routes.test.mjs(10 项):用假 ctx 真跑 apply()、拿它注册的路由、用桩 fetch 真打一遍 —— 覆盖路由清单、天气取值(城市来自设置)、失败文案、余额 401、投影撞名时 apply() 不得整体失败(零功能跳过),以及宿主与浏览器半侧的投影键必须一致(写错就是费用 pill 静默失灵)。这层测试正好补上开发期踩到的一个缝:天气的模块级函数误读了 applyInner 闭包里的 resolveConfig,单测全绿而真机 502(resolveConfig is not defined)—— 现在 scripts/verify-dsh-0.1.7.mjs 也加了一条「失败信息里不得出现 JS 层错误」的断言。isPeakBeijing() 只判了周末、没有节假日表 —— 于是春节/国庆这类落在工作日的长假会被按高峰算,1M 未命中输入 + 1M 输出在 9/10 档下会从实际的 5 元虚报成 10 元。CN_STATUTORY_HOLIDAYS(lib/usage.js),按国务院办公厅《关于 2026 年部分节假日安排的通知》(国办发明电〔2025〕7 号)逐条录入 2026 年 33 个放假日;客户端 lib/client.js 的 isPeakNow() 同步镜像同一张表(浏览器半侧拿不到宿主模块,只能复制,已加注释互指)。test/holiday-peak.test.mjs(10 项):节假日整天低峰、调休补班日低峰、节前节后工作日不受影响、北京时间跨日切分、非法时间戳不抛错、宿主与客户端两份实现逐时刻对拍(2026 全年 + 2027 初,约 4700 个时刻,含 09:00/12:00/14:00/18:00 边界分钟)、以及端到端断言"节假日 5 元 vs 工作日 10 元"。handleEnded 里工作中只列举了「开始工作 / 点击回应 / 拖拽」几种动画,其余一次性动画播完一律掉进随机链 pickNext();而 busyRef.current 此时已经是 true,后续 mood:'working' 会被 if (!busyRef.current) 挡成空操作 —— 宠物就永远随机下去,而 Agent 的状态早已不再变化(agent/status 是边沿触发,只发变化)。多 Agent 交错(子代理 running/idle 穿插)、或叫醒/通知动画被打断时最容易踩到。现在:工作中任何"非工作链"动画播完都直接回到工作轮播。GET /api/whale-pet/state 每次轮询都带上从 Agent 注册表现算的 running 布尔;客户端每 800ms 对一次账——事件丢了会被自动纠正,busy 与动画脱钩(卡在待机链)也会被拉回工作轮播。applyMood('idle') 是直接跳到「待机」,把「工作结束」这一态整个跳掉了;现在只有真的从"工作中"退出时才播它(本来就空闲时不会多播一次),并且同一批次里的完成气泡不会让同一个动画重播(playNotice 遇到当前已在播的动画只出气泡、不重启视频)。/state 带 running 的断言)。适配 DSH 0.1.7-alpha.1(三处破坏性变更,0.3.2 在 0.1.7 上整体失效):
dsh-settings:SettingsProvider → SettingsForms,ctx.settings.register() / get() 被删除。老代码在 apply() 里同步抛 TypeError;dsh-jobs:ctx.jobs.onJobDone() 被删除,改成 ctx.jobs.events.subscribe(filter, listener) + settled 事件(job 投影 / cause);dsh-shell:run(spec) → execute(spec),且 execute() 返回进程句柄,前台结果要再 await handle.result()。任一处在 apply() 里抛错,DSH 就判定该 entry "did not activate",表现是桌宠整个消失(连浏览器半侧都不挂)——0.3.3 的第一版适配只修了 settings,漏掉 jobs/shell 时就是这个症状。现在:
Config 逐字段标 .volatile()(0.1.7 只把 volatile 字段放进设置表单,也只有它们能被 mutate() 写回);ctx.fiber.config 递归解包 volatile 引用(注意:volatile 是逐字段包装、只有 .get(),写入用 Symbol.for('cosmokit.volatile.write'),没有 .set());entryIdOf() 从 loader entry 取,退回 pet),不再是写死的 whale-pet;jobs.events.subscribe({ owners: 'all' }),只在 settled 且 cause !== 'teardown' 时提醒;老版本退回 onJobDone;runShell(),兼容 execute()+result() 与旧版 run();safe(ctx, label, fn) + apply 外层兜底):以后单个 API 漂移只丢那一个功能并记一条 warn,不再让桌宠消失;peerDependencies 升到 ^0.1.7-alpha.1。修复:内部统计的金额不含子会话。DSH 的 costUsage 投影只折叠本条会话自己的日志,子代理是独立会话,所以会话费用 pill / 本轮费用 pill 天然漏掉它们。现在:
lib/usage-ledger.js)额外按会话 id 记一份累计(sessionCost(id),与时间桶同步加/减,替换语义一致);lib/subtree.js(纯逻辑)按 parentSession 血统建会话树、汇总全部后代会话的开销;在线会话取不到的子代理由 sessionPersistence 的落盘 header 补全;GET /api/whale-pet/subtree-cost?session=<id>;会话 pill 把后代合计叠加进总额,本轮 pill 按"子会话创建时刻落在哪一轮"归到该轮,明细里单列「子会话」。修复:同时查询余额和今日用量的按钮(💰)。除了上面那条让路由重新注册,今日花费的口径也从"只扫当前在线会话"改成读分时段账本:覆盖所有会话(含子代理)并带历史补扫,重启后今天早段的花费不再丢;账本不可用时自动退回旧口径。dashboardHistory 从关改成开时也会按需补跑一次历史扫描。
修复:数据看板 / 费用弹层背景变成半透明(看穿了)。DSH 0.1.7 把 --dsw-specific-menu 从 rgba(248,249,250,.94) 改成了半透明(浅色 .58 / 深色 rgba(48,49,54,.5)),并且样式规范要求"用这个填充的高层级表面必须在同一条规则里应用 backdrop-filter: var(--dsw-menu-backdrop-filter)"(官方 ui-chat 的 stat-dialog.module.css 就是成对写的)。宠物这两块面板只取了颜色、没配滤镜,于是就"看穿"了。现在改用官方 Modal 内容同款的不透明层表面 --dsw-alias-bg-layer-2(配同一个 --dsw-elevation-prominent),并回退到同样不透明的 --dsw-alias-bg-module-platform 以防 token 再漂移。实测:浅色面板 rgb(255,255,255)、深色 rgb(44,44,46),内层统计卡 rgb(245,246,247) / rgb(53,54,56) 仍有层次,backdrop-filter: none。
单测 76 → 88 项:新增 test/subtree.test.mjs(会话树 / 后代枚举 / 环形血统防御 / 按轮归集 / 账本按会话累计 / "今日用量含子会话"回归)。
新增 scripts/verify-dsh-0.1.7.mjs:对着真实的 dsh-settings 源码跑 30 项兼容性自检(API 形状、apply() 激活、inject 全覆盖断言、零功能跳过断言、7 条路由、子会话计费端到端、volatile 解包、jobs 事件流、shell execute()/result()),下次 DSH 升级可直接复用。
新增 .research/pet-diag-probe.mjs(工作区里的诊断探针):把 profile 起在隔离端口 3099,打印启动输出里的 did not activate / TypeError、用 token 换 cookie 逐条打桌宠路由 —— 就是它定位出 jobs.onJobDone is not a function 的。
InputBar.module.css 的 .dock{display:flex;align-items:center;justify-content:center;gap:12px}),并把「上下文占用」计也放进这一行;而本插件的费用条目还在用旧布局的覆盖式定位(width:100% + max-width + margin:-20px auto 0 + padding + justify-content:flex-end)。进了横向 flex 行之后,负 margin 会把自己整块上移 20px,width:100% 还会挤扁同排的官方 stats / 上下文条目 —— 这就是错位。现在它就是一个普通行内 flex 项(display:inline-flex;flex:none;align-items:center),间距与垂直居中交给官方 dock,和官方条目自然同排。peerDependencies 里的 DSH 包从 ^0.1.0-rc.6 更新为 ^0.1.6-alpha.2(按 semver 的预发布规则,旧范围不满足 0.1.6-alpha.2,pnpm install 会提示未满足 peer);README 增补兼容性说明。宿主半侧经实测确认(/api/whale-pet/usage 正常返回),costUsage 投影、sessionPersistence 补扫、四个槽位注册与官方 API 均无破坏性变更;本轮费用 pill 的尺寸口径与官方 TurnUsagePanel 仍逐项一致。width:100% / margin:-20px / --dsh-chat-content-width / --dsh-composer-side-clearance 等旧布局写法)。scripts/sync-install.ps1:本机同时存在两份已安装副本——~/.dsh/profiles/node_modules/dsh-whale-girl-pet(根级)与 ~/.dsh/profiles/web/node_modules/dsh-whale-girl-pet(web profile 真正加载的那份)。只同步一份就会出现"重启了但现象没变"(本次就先把修复同步到了根级那份,白重启一次)。脚本现在默认同步全部已存在副本、收尾逐字节校验,并提示哪一份是 profile 实际加载的。place({}),那个空对象被当成尺寸覆盖参数,而位置仍取自内存里的旧布局,于是只"清了记忆、没改位置"。现在复位会先清空内存布局、再按空布局落定,当场回到默认尺寸 + 视口居中。scripts/sync-install.ps1(开发用):把工作区同步到已安装副本并校验,同时判断当前 dsh web 进程是否需要重启。profile 里装的是副本而非软链,漏同步会造成"重启了但现象没变"的假象(0.3.0 收尾时就踩过一次)。今日分时(北京小时轴)/ 近 7 天(北京日轴)两种视图,花费 ↔ token 一键切换localStorage,记忆带版本号GET /api/whale-pet/usagelib/usage-ledger.js:分时段账本(北京小时桶 + 日桶、调用身份去重、替换语义、保留窗口),零依赖看板历史补扫、看板窗口天数portalContainer() 校验(避开 React #200,该错误会打挂整个 shell.overlay 导致桌宠消失);看板包在错误围栏里assistant/message.usage + assistant/message / assistant/attempt 嵌入 stream 的 usage),重试按两次调用计费;周末全天按空闲计价。lib/usage.js(零依赖)与 lib/cost-projection.js,加 32 项单测。snapshotEvents 取代 events getter),修复花费/用量恒为 0。dsh-settings settingsNamespace 导出的依赖(改用字面量命名空间)。dsh-whale-girl-pet/
├── lib/
│ ├── index.js 宿主半侧:/pet 动画路由、/api/whale-* 接口、settings 命名空间、投影注册
│ ├── usage.js 用量与计费内核(价目表 + 事件折叠 + 三桶费用,零依赖)★唯一计费口径
│ ├── usage-ledger.js 分时段账本(北京小时/日桶、去重、保留窗口)—— 看板数据源
│ ├── cost-projection.js whalePetCost 会话投影(费用 pill / 本轮费用 pill 读取)
│ ├── client.js 浏览器半侧:桌宠本体、气泡、按钮组、看板弹窗与布局、设置面板
│ └── types/ TypeScript 类型声明(纯类型,不影响运行时)
├── assets/thumb/ 360×360 播放用动画(随包发布)
├── assets/preview/ README 预览图 / 收款码
├── scripts/
│ └── sync-install.ps1 开发用:把工作区同步到已安装副本,并判断要不要重启 dsh web
├── test/ 单测(node --test,不发布)
└── cordis.patch.yml bundle patch:把插件行挂进 DSH 配置树
唯一的计费口径:lib/usage.js。任务完成气泡、余额按钮、会话/本轮费用 pill、数据看板四处的金额全部由它算出,所以它们永远一致。
node --test "test/*.test.mjs" # 跑单测(76 项,零依赖,只用 node:test)
客户端 bundle 是手写的 window.__ModuleLoader__.load({ id, factory }) 形态,零构建步骤。
⚠️ 改完必须同步到已安装副本,再重启 dsh web:profile 里装的是副本(不是软链),插件 bundle 又在进程启动时就载入内存。漏掉同步会出现"重启了但现象没变"这种极难查的假象(本仓库开发过程中真的踩过一次)。脚本一步搞定,并顺带判断当前进程是否需要重启:
pwsh -File scripts/sync-install.ps1 # 同步 + 校验 + 提示是否需重启
pwsh -File scripts/sync-install.ps1 -CheckOnly # 只校验(有差异时退出码 1)
确认页面跑的是哪版代码:浏览器控制台打印
localStorage.getItem('dsh-whale-pet.dashboard-layout'),看记忆版本号(当前 v:5)。
计费口径改动请同步更新 test/usage.test.mjs / test/usage-ledger.test.mjs;看板布局改动请更新 test/dashboard-layout.test.mjs(它用 vm 从 client.js 里抠出工厂源码来跑,测的就是线上那份代码)。
喜欢这只桌宠的话,可以投喂她吃口 Token(完全自愿,不影响任何功能)~
制作新动画视频请参考 dsh-pet。
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: desktop-pet、web-ui。