dsh-web
zhu1090093659/dsh-webDeepSeek Harness(DSH)Web 插件聚合生态包 · 一切皆插件,创意工坊分发(dsh-market.com)
🐋 DeepSeek 娘桌宠:住进 DeepSeek Harness Web 界面的蓝发鲸鱼女仆。工作链路、任务完成统计(用时/消耗/花费)、睡眠系统、时间感知、余额/天气/喂食按钮、完整设置面板。
一只住在 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 + 今日花费(峰谷分开计费,并按命中/未命中/输出三桶拆分),调用官方余额接口。顺手播一段「翻钱包」动画,结果汇入头顶气泡。
账号登录也能查:配了
DEEPSEEK_API_KEY时走官方api.deepseek.com/user/balance(既有行为);没有 Key 或 Key 失效(401/网络失败)时自动回退到 DSH 登录账号的开放平台钱包(ctx.deepseekAccount.getBalance(),充值余额 + 赠金余额合计)。所以 DSH 只登录 DeepSeek 账号、不配 Key 也能正常显示余额;两条路都没有时气泡会同时提示"没配 Key / 没登录账号"。
今日花费 = 内部统计,含子代理会话与重启前的历史:数字取自分时段账本(实时折叠所有会话 + 启动/按需补扫已落盘会话),而不是"当前在线会话"。所以主会话派发出去的子代理(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 梗台词 | 开 |
| 漫游走动 | 关闭后桌宠不乱跑 | 开 |
| 按钮组(☁️💰🍪📊) | 放在宠物左侧 / 右侧,或关闭整组(关闭后宠物、费用 pill、角标与提示音照旧,随时可开回来) | 左侧 |
| 提醒气泡 | 任务完成/失败、回来汇总、天气/余额/喂食结果、番茄钟/深夜关怀/随机小剧场这些头顶气泡的总开关;关掉只影响显示,动画、角标与提示音照旧 | 开 |
| 工作输出气泡 | Agent 干活时头顶逐字打出的当前动作;关掉后只留动画,任务完成提醒不受影响 | 开 |
| 看板历史补扫 | 关掉则只统计本次运行的用量 | 开 |
| 看板窗口天数 | 日趋势保留并展示的天数 | 7 天 |
| 长任务提醒阈值 | 工作超过 N 分钟提醒 | 10 分钟 |
| 天气城市 | 留空 = 自动定位 | 空 |
| 费用显示:会话费用 pill | 输入框下方那条「费用 ≈¥x.xx」(与 dsh-cost-meter 重合时可关) | 开 |
| 费用显示:本轮费用 pill | 每条回复动作行里的本轮费用 | 开 |
| 费用显示:任务完成气泡花费 | 气泡里的花费与三桶(关掉仍保留「用时/消耗」) | 开 |
| 费用显示:💰 余额按钮 | 余额 + 今日花费气泡(与 cost-meter 的余额/今日花费重合) | 开 |
| 费用显示:📊 分时段花费看板 | 小时/日趋势看板 | 开 |
| 尺寸 | 宠物大小(px),可直接填数字或拖动编辑框;☁️💰🍪📊 按钮会跟着等比缩放(按钮 12-72px,默认尺寸下仍是原来的 38px) | 260(40-400) |
| 位置 X / Y | 宠物外框左上角距视口左上角的像素坐标;两个都填 = 固定位置,留空/「恢复默认角落」= 跟随下方「默认角落」并随窗口自适应 | 未设置(跟随角落) |
| 默认角落 | 未设置固定位置时贴哪个角(右下 / 左下) | 右下角 |
| 隐藏宠物 | 收起宠物本体,改成贴浏览器边缘偷看(素材 assets/thumb/peek-edge.png,尺寸仍跟随「尺寸」设置;贴哪边跟随角落设置,设了固定位置则贴较近的一边);把她拖到屏幕左/右边缘也会自动收起来贴边偷看(认出是哪一边、并停在你放手的高度,边与高都会记住,重启后不会换边也不会跳回底部);动画暂停、不漫游、点击无反应,点她一下即可叫出来;☁️💰🍪📊 按钮、费用 pill、标签页角标与提示音照旧;通知气泡照常(完成/失败/回来汇总、天气/余额/喂食的结果都会浮在内侧,只有"工作中"的打字气泡不出现;「提醒气泡」开关关掉后这些也都不出) | 显示中 |
位置与大小怎么调(issue #10):「桌宠配置 → 宠物与外观 → 位置与大小」里可以直接填数字后点应用,也可以点拖动调整位置与大小 —— 设置面板会自动收起,宠物外围出现一个虚线编辑框:拖框体移动、拖四角缩放(保持正方形、对角固定),工具条上实时显示
尺寸 · X · Y,点 保存 写入设置(即时生效,刷新/重启后保持),点 取消 或按 Esc 还原。工具条按钮是文字(不用 ✓/✗ 这类符号:部分字体栈里它们缺字形,按钮会变成空白),并且带内联样式兜底,任何情况下文字都可见。口径说明:X/Y 是可见外框左上角的视口像素坐标(不是宠物中心、也不含脚底对齐偏移),所以"框在哪 = 填的数字 = 宠物在哪"。「取当前位置」会把宠物现在的坐标填进输入框;窗口变小导致已存坐标越界时,宠物会按视口重新夹取(不会跑出屏幕),下次点「应用」即落盘为夹取后的值。若只想改大小、不想把位置固定住,不要动 X/Y 输入框,直接点「应用」即可(位置继续跟随角落并随窗口自适应)。
与 dsh-cost-meter 共存:两个插件在「本会话费用 / 今日花费 / 余额 / 峰谷」上有功能重合(它还有预算、Coding Plan 额度、价目同步等我们不做的东西)。所以 0.3.6 起把我们这边每个显示费用的位置都做成了独立开关(上表最后五行,默认全开)。关掉只影响显示 —— 费用投影、分时段账本与所有
/api/whale-pet/*路由照常统计,随时开回来即可。另外我们的会话投影键已改为whalePetCost,不会再和它的costUsage撞键(issue #1)。
电脑上印着鲸鱼剪影 logo,工作链动画同一会话生成保证一致。
buttonSide 由 left/right 扩成 left/right/off(复用字段,旧配置原样生效;非法值归一成 left),选「关闭」时整组 ☁️💰🍪📊 不渲染,宠物本体、费用 pill、角标与提示音照旧;头顶气泡拆成 bubbleNotice(任务完成/失败、回来汇总、天气/余额/喂食、番茄钟等提醒)与 bubbleWork(干活时逐字打出的工作输出)两个独立开关,默认全开。三个显示项改成订阅设置快照即时生效(值不变不重渲染),关掉时立刻收起在途气泡/看板,重新打开且 Agent 在干活时接回打字机。DEEPSEEK_API_KEY(或 Key 失效 401/网络失败)时,/api/whale-balance 自动回退到 DSH 登录账号的开放平台钱包(ctx.deepseekAccount.getBalance(),充值余额 + 赠金余额合计);有可用 Key 时行为完全不变。两条路都不通时气泡同时提示"没配 Key / 没登录账号"。/api/whale-*、/api/whale-balance、/pet/* 每条路由第一行都调 ctx.connection.requestRejection(req)(DSH Connection 的 Host/Origin fence 挡 DNS rebinding 与跨站请求、浏览器登录 cookie 鉴权;401/403)。此前这些路由在 DSH 鉴权之外(无 token 也能读余额/用量、POST 改设置)。设置 POST 另加 application/json 限定(415)、ops 形态校验(400),并在 SECURITY.md 补安全模型说明。lib/types/index.d.ts 补全 Config 字段(含 posX/posY/hidden/bubbleNotice/bubbleWork/cost*/notify*)并修正 whalePetCost 命名;中文 README 设置面板概述同步三态按钮组与气泡开关;测试 175 项全部通过(新增账号余额 4 项 + 余额整形 5 项 + 路由鉴权/设置 POST 4 项)。tool-pwsh / tool-bash 注册后台任务时把整条 args.command 当 job.label,旧实现直接 '「' + label + '」' 拼进气泡 —— 357 字符的命令能占满一屏。
[ PowerShell 命令 · 用时 3 秒 ] 搞定啦~,命令原文不再进气泡。kind 按 DSH 现有的四种映射(bash → Shell 命令、pwsh → PowerShell 命令、pty-send → 终端命令、workflow → 工作流);未知 kind(本插件订阅 owners: 'all',也会收到别的插件起的 job)只在 label 折叠空白后 ≤24 字时才当"名字"显示,超长一律退回「后台任务」—— 不做"像不像命令"的猜测,只按长度切;起止时间缺失时只显示类型,不再出现"用时 0 秒"。completed(tool-pwsh/background.ts 的 processOutcome():非 killed 一律 { status: 'completed', detail: 'exit code: N' }),所以 exit 9 的命令以前同样说「搞定啦~」。现在显示 [ Shell 命令 · 用时 9 秒 ] 退出了:exit code: 9 并按失败推送(气泡走失败配色与提示音);exit code: 0 仍是「搞定啦~」,detail 里的沙箱说明一并保留。「」 缺字形:部分字体栈里 「」(U+300C / U+300D)没有字形、渲染成方框。宿主 6 处气泡文案 + 浏览器半侧 4 处用户可见文本(打字气泡的"正在调用…",以及设置面板里位置与大小 / 取当前位置 / 气泡花费的说明)全部换成 ASCII [ ];源码注释不动。npm test)。test/host-routes.test.mjs 补上了 jobs 事件流桩与 ctx.get('jobs') 分支 —— 这条路径原先零覆盖(假 ctx 里 ctx.get('jobs') 恒为 undefined),issue #11 正是从这个盲区出来的。新增断言:气泡里不得出现命令的任意 40 字子串、label: null 不得渲染出 null、非零退出必须 ok:false、message/title 不得含 U+300C/U+300D、cause: 'teardown' 仍被忽略。scripts/dsh-resolve.mjs(跑 node --test 时把宿主提供的依赖 —— @deepseek-ai/* 与 zod —— 从 DSH 安装 / 检出里兜出来),package.json 补 scripts.test;scripts/verify-dsh-0.1.7.mjs 的运行说明补上第二个 --import,否则在没装 node_modules 的工作区会先报 ERR_MODULE_NOT_FOUND: zod。size / position 配了不生效,界面上也无处可调。两个字段在宿主 schema 里定义了、也标了 volatile,但客户端 Pet 一直从空的客户端 config 参数读它们(DSH 这条管线尚未打通),于是永远走 || 260 / 'bottom-right';而 0.2.x 起"插件配置该放哪"改成了插件自己在 Plugins 页注册 plugins.bundle.config / plugins.row.config,已经没有任何"自动把 volatile 字段画成表单"的兜底渲染器 —— 于是 test/notify.test.mjs 里那条「size / position 由 DSH 原生表单负责,豁免」的前提失效,两个字段掉进了夹缝:宿主有值、镜像里有描述符、界面上没人画。
position 第一次真正可达);「恢复默认角落」用 unset 清掉固定位置、回到随窗口自适应的角落。尺寸 · X · Y:保存 写入设置(即时生效,刷新/重启后保持),取消 或 Esc 还原。工具条按钮一律用文字 + 内联样式(✓/✗ 这类符号在部分字体栈里缺字形,会让按钮变成空白;内联样式则保证背景/颜色没生效时文字也看得见)。编辑期间宠物不漫游、不响应自身点击/拖拽(位置交给编辑框),☁️💰🍪📊 与气泡一并收起,免得挡住手柄。posX / posY 是可见外框左上角的视口像素坐标;两个字段都有值才算"固定像素位置"(此时舞台不再套脚底对齐偏移,所以"框在哪 = 填的数字 = 宠物在哪"),任一缺失就回落到 position 角落、由 CSS 决定位置并随窗口自适应。Y 的上界取"脚底线刚好落在视口底边",该值恰好等于角落模式换算出的坐标 —— 两种模式互切时宠物不跳一下。usePetGeometry()(只订阅 size / position / posX / posY,避免每 800ms 轮询都让 Pet 整棵重渲染)、模块级 petGeometry(实测外框,供面板预填与编辑起点)与 petEditor(编辑会话,面板与桌宠分属两个槽位组件,只能靠模块级单例通信)。保存走既有的 POST /api/whale-pet/settings,不新增任何宿主路由;设置不可写(远程 Web)时整块控件禁用并给出说明。--wb-unit = 宠物尺寸 × 38/260 推出来并夹在 12-72px:260px 时逐像素还原旧版(38/8/60/-52/17),400px 时 58px,40px 时 12px(emoji 字号另有 8px 可读下限)。气泡与编辑框工具条有意不缩放 —— 它们是阅读/操作界面,字号跟着宠物变会不可读。assets/thumb/peek-edge.png,由宿主既有的 /pet/thumb/<文件> 路由提供,不新增路由):左缘就是原图里被墙裁开的那一刀,所以贴左边界时天然像"从屏幕边探出来",贴右边界时水平镜像;尺寸仍跟随「尺寸」设置(与按钮组同一套缩放口径),按钮组自动让到画面内侧挨着她。点她一下即可叫出来(写 hidden=false),不必再进设置面板。
24px 以内松手,就自动写 hidden=true 收起来贴边偷看。判定用松手时的指针横坐标(拖拽时宠物跟着指针居中,所以"指针贴边"等价于"宠物被顶到边上")。竖直方向跟随放手的高度(贴边不再永远贴底,夹取范围是"整张图留在视口内")。同时会把 position 与 posY 一起写好 —— hidden 是持久化设置,重启后客户端只能靠这两个字段知道该贴哪一边、贴多高,只写 hidden 会出现"明明拖到左上角、重启后贴在右下角"。边与高的选择顺序都是"先会话、后设置":边看 会话内的拖拽位置 → 设置里的固定像素位置 → 角落设置;高看 会话落点 → 设置里的 Y(hasPosY,允许只写 Y 不写 X)→ 都没有才贴底。ended 自然停住)、不漫游、点击/双击/拖拽无反应。保留的:☁️💰🍪📊 按钮(含 📊 看板弹层)、输入框下方与本轮两个费用 pill、favicon 角标、提示音 —— 它们本来就不在宠物那一层(两个 pill 是另外注册的槽位组件,角标/提示音是独立模块)。恢复显示时把前台缓冲重新 play(),动画从原处继续。40px(PEEK_PULL_OUT_DISTANCE)才把她拽出来,之后才跟手跟着指针走。横向阈值按贴边方向取分量(贴左边时"往右"为正,贴右边时"往左"为正),所以"贴着边上下挪"永远不会误触发出场。拽出来的落点同样记进会话位置;拖完会抑制随后的那次 click,不会重复写设置。
pointermove 里没按键(buttons === 0)一律当成手势已结束并自愈 —— 指针在浏览器窗口外面松开时 pointerup 可能收不到,状态一直挂着的话,之后鼠标只是划过图片就会让宠物跟着光标跑。video 那边同样补了"失去指针捕获"的收尾。尺寸 × .2225(260px 时约 58px)—— 居中对齐等于一半气泡被屏幕边切掉(真机反馈过"气泡被遮挡")。现在与按钮组同一套口径:从她身后(图片宽度 = 尺寸 × .445)再往内 6px 起排,居中位移归零,整条气泡都在视野内。opacity:0 而不是 display:none:舞台盒子在位才能让按钮组有布局依据;opacity 会作用到整棵子树(含 stage 的 shadow root 里的 video)。另外给隐藏的舞台补了 pointer-events:none —— 根节点是 z-index:40,不可见但仍在吃鼠标的话会挡住下面的界面;贴边时根节点的定位交给 CSS 类(is-peek-left/right),因为内联样式优先级更高、会盖掉贴边规则。pet 行」的区别:那样会把费用 pill、看板、设置面板、宿主路由与费用投影一起干掉(它们同属一条 loader 行),正是报告人做不到"只隐藏宠物"的原因。G≈156-169,G−max(R,B)≈85),管线很短也很稳:按墙右缘黑线(x≈422-426)裁掉左侧 → 缩放到目标高度 → 软色键(绿余量 G−max(R,B) ≤8 全不透明、≥55 全透明,中间线性过渡,得到抗锯齿边缘)→ 去绿边(前景像素的绿通道压回 max(R,B)+6 以内)→ 只保留最大连通块(顺手丢掉右下角水印与残余墙线)→ 按 alpha 裁到人物 → 输出 RGBA PNG(316×712,382KB,半透明边缘 1395 px,残留绿边 0 px)。
(255,250,248)),只比背景多一点暖色;她又正好在左边缘被墙裁开,皮肤与背景连通 —— 于是填充顺着脸漏进去,把额头、脸颊、下巴一起抹成透明(反馈"脸被扣掉了")。第二版加了"偏暖即皮肤"的保护并做了两遍填充,脸保住了,但白色衣领这类中性白仍会被少量误伤,边缘也容易留浅色残留。换绿幕素材后这些问题一次性消失:色键判据(绿占优)与人物配色(蓝/白/肤色)完全不冲突。test/pet-geometry.test.mjs(7 项,从 lib/client.js 抠真实纯函数源码跑:尺寸/坐标夹取、自定义位置判定、拖框、拖角、以及"角落↔自定义不跳"的口径一致性);test/notify.test.mjs 去掉 size / position 豁免(这条强不变式从此会挡住"加了配置却忘了给入口");test/client-bundle.test.mjs 新增编辑框接线不变式(尺寸跟 --dsh-pet-size、四角手柄 touch-action:none、保存/取消/Esc 三个出口、工具条按钮必须是文字且带内联样式、保存失败不退出、编辑期收起按钮组)与"贴边偷看"素材/接线不变式(PNG 必须是带 alpha 的 RGBA、竖构图、走既有路由、左右贴边与镜像、按钮让位);test/host-routes.test.mjs 新增"posX/posY 无默认值且 volatile"与"set/unset 原样透传"断言。(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 照旧可用。