dsh-signal-pet
Manifest validA status pet for DeepSeek Harness: reacts to the real session event stream (thinking, tool calls, errors, long-job completion) and doubles as a busy/error indicator you can see at a glance.
dsh-signal-pet
一只住在界面角落的小宠物,它读你真实的会话事件:思考时动得更快,有工具在跑时拉出连线,出错时变红发抖,长任务做完时庆祝一下。
A status pet for DeepSeek Harness. It reacts to the live event stream — thinking, tool calls, errors, long-job completion — and doubles as the busy/error indicator you can actually see at a glance.
它解决什么
"宠物"这个形式的问题从来不是不好玩,而是看完不知道该看什么。所以这只宠物有两个身份,而且是同一个东西:
- 好玩的那面:它会动,会庆祝,点一下会收起来。
- 有用的那面:它同时是一眼可见的忙碌/出错指示器。你不用去翻日志、也不用盯着转圈——出错了它就是红的,在抖;在跑工具它就是紫的,有几条线就有几个工具在跑。
它不做内容识别、不做装饰、不挡你打字。位置会主动避开输入框——一只趴在你正在打字的框上的宠物是障碍物,不是装饰。
状态
宠物的状态不是客户端猜的。宿主半区监听真实的 harness 事件,折叠成一个小快照,客户端只负责画:
| 状态 | 触发 | 表现 |
|---|---|---|
idle | 没有活跃会话 | 慢慢上下飘 |
thinking | agent/status 变 running | 同样的动作,速度快 3 倍多 |
working | 有工具正在执行 | 紫色环 + 每个运行中的工具拉一条流动的连线 |
error | tools/result 报错,或 agent/error | 红环 + 抖 5 下 + 显示错误摘要 |
celebrate | 一个跑了 10 秒以上的回合结束 | 跳 4 下 + 三颗火花 |
优先级是产品决定,不是巧合:错误压过一切。哪怕同时在跑工具,只要刚出过错,宠物就是红的——它的职责就是"出事的时候你一定会注意到"。
装
# npm 版 CLI 管理的 profile
dsh plugin --profile <name> add <插件目录>
# 桌面版:desktop profile 由 Electron app 独占,npm 版 CLI 会被拒绝——
# error: profile "desktop" is managed exclusively by the Electron application
# 用它自带的那个 CLI 才行(在 app 的 resources/runtime/cli/bin/dsh 下):
"<app>/resources/runtime/cli/bin/dsh" plugin --profile desktop add <插件目录>
装完重启一次 app(host 半区必须重启进程才会加载)。
用
- 点一下宠物 —— 收成一个小圆点,只留状态环颜色;再点一下展开。选择记在
localStorage。 data-pet-state—— 宠物根元素和图层上都带这个属性,值就是当前状态(idle/thinking/working/error/celebrate),所以截图验收、自动化断言、CSS 钩子都有稳定的抓手。data-pet-mode—— 图层上是animated/static/collapsed。
避让 UI
宠物的位置在五个候选角里挑第一个完全不重叠的;都不行就挑重叠最小的,并把 clear: false 落成一个类名(vp-blocked),这样界面能说明它没能让开,而不是假装让开了。
障碍物是按行为识别的,不是按 DSH 的类名:
textarea, input, [contenteditable="true"], [role="textbox"], [data-pet-avoid]
外加当前获得焦点的元素(哪怕它不是上面任何一类)。data-pet-avoid 是留给其他插件和你自己的逃生口——任何你想要宠物避开的东西,加上这个属性就行。
匹配行为而不匹配类名,是因为 DSH 的界面样式下一次重构时,类名会变,而"这是一个输入框"不会变。
无障碍
prefers-reduced-motion: reduce 时,宠物退化成静态角标:所有 keyframe 关闭,身体缩成 22px 圆点,但状态环的颜色和文字标签保留。
这是刻意的取舍——减弱动效不等于减弱信息。指针仍然必须能传达"现在在忙"和"刚才出错了",只是不再靠运动传达。
和原版的关系
灵感来自 9livewolf/dsh-think-bounce-pet(17 stars)。
原版没有任何 LICENSE 文件,也就是默认「保留所有权利」。所以本插件没有复用它的任何一行代码,也没有 clone 它——只读了它的 README 来弄清它做了什么、缺什么,实现是从零写的。这一点值得写清楚,不是客套。
原版实际做了什么(据它的 README):两只大肥鱼在界面里弹跳,作者自己写的是「无实意」;有常驻球和思考球(推理开始时飞入、结束时缩小消失);支持鼠标碰撞、重力、初速度、摩擦、大小等实时参数;有设置页;参数存在 localStorage;挂在 shell.overlay,全屏 pointer-events:none 图层。
所以差异不在"接不接会话状态"——原版确实接了思考状态。差异在接得有多细、以及有没有把它当指示器用:
| 原版 | 本插件 | |
|---|---|---|
| 会话状态 | 只有推理开始/结束 | agent/status、tools/pre-execute、tools/result、agent/error、agent/turn-stopping 五种事件 |
| 工具调用 | 不反映 | 每个运行中的工具一条流动连线,data 里带次数与最近一次的名称/耗时/是否失败 |
| 错误 | 不反映 | 红环 + 抖动 + 错误摘要,且优先级最高 |
| 完成 | 不反映 | 跑满 10 秒的回合结束时庆祝一次 |
| 避让 UI | 不避让 | 五候选角落 + getBoundingClientRect 避开输入框与焦点元素 |
| 无障碍 | 不响应 | prefers-reduced-motion 退化成静态角标(保留状态颜色与文字) |
| 可验收 | 无稳定抓手 | data-pet-state / data-pet-mode |
| 物理 | 重力、抛出、鼠标碰撞、实时参数面板 | 没有 |
| 设置页 | 有 | 没有(只有一个点击收起) |
最后两行是原版比我强的地方,直说。我没有实现物理引擎,因为那部分不服务于"一眼可见的指示器"这个目标,而重写一个物理系统也不是这次改进要解决的问题。
HTTP 面
GET /dsh-signal-pet 当前快照(phase、计数、最近工具、错误、忙碌时长)
GET /dsh-signal-pet/state 同上(别名)
POST /dsh-signal-pet/reset 清空状态
POST /dsh-signal-pet/pulse 强制触发一次庆祝(用来验收动画,不用真的等一个长任务)
它绝不干扰工作
这一条是硬约束。tools/pre-execute 是决定一个工具会不会被执行的 waterfall,所以宿主的监听器在任何路径上都会到达 next()——包括它自己内部出错的时候。一个能挡住它所报告的工作的状态指示器,比没有指示器更糟。
其余同理:状态折叠、事件记录、路由注册全部包在 try/catch 里,任何一处失败都只是宠物不灵,不会让回合失败。
容错姿态
webServer.register失败只是没有 HTTP 面(宠物不显示),事件监听照常工作。- 宿主侧
enabled: false时接口照常应答,只是不再记录事件。 shell.overlay注册失败只让这一层降级并打日志,不会让整个客户端启动失败。- localStorage 被禁用只影响"收起"这个偏好,不影响宠物本身。
- 窗口尺寸为 0 时位置计算返回有限数值,不产生 NaN。
- 每次注册都可撤销(
ctx.effect),因为 dsh 能在运行期卸载插件。
配置
apply(ctx, config) 的 config 全部可覆盖:
| key | 默认 | 说明 |
|---|---|---|
errorTtlMs | 12000 | 错误在宠物上停留多久,之后回落到"现在正在发生什么" |
celebrateMs | 2600 | 一次庆祝持续多久 |
celebrateAfterMs | 10000 | 回合至少跑这么久才值得庆祝。低于它就只是普通结束,每次都庆祝等于噪声 |
maxTethers | 6 | 最多画几条连线(同时跑 20 个工具时不该糊满屏幕) |
enabled | true | 关掉后事件不再记录 |
开发
node --test # 67 个测试
node scripts/build.mjs
状态机是纯的(src/signal.js:无定时器、无 I/O、不改输入),所以"什么时候该庆祝""错误什么时候该消"这类判断全部由单测钉死,不需要跑起来看。
阶段是每次读取时推导出来的,不是存下来的。 错误会过期、庆祝有窗口,如果把 phase 当状态存,就需要一个定时器去清除它——而依赖定时器的状态机,总会有一次定时器没响然后永久卡在 error。
避让定位和动效降级也是纯函数,通过 __test 暴露。这两块最典型的坏法都是"看起来没问题":宠物悄悄不再避让输入框,或者对要求减少动效的人继续动。它们不需要 DOM 就能验,所以就被验了。
渲染层也被真的渲染过一遍(test/client.test.mjs:结构化 stub 掉 React 与浏览器全局,然后调用注册进去的组件,像 React 一样执行函数组件再走整棵树)。这不是为了覆盖率好看——data-pet-state 和 reduced-motion 降级这两个承诺只存在于渲染结果里,而"看着没问题"正是一个坏掉的指示器最常见的存活方式。
License
MIT
Comments
Loading…
Similar plugins
by Jimmy0123-ux
Bilingual floating DeepSeek Harness pet with live context, lifetime token ledger, trends, opt-in completion sound, and editable Prompt Optimizer enhancement.
★ 54
↓ 361/wk
MIT
TypeScript
Sep 21, 2026
dsh plugin --profile web add dsh-token-petby hasan-aghayev
Floating desktop pet for DeepSeek Harness with a growing local pet library
★ 0
NOASSERTION
JavaScript
Sep 23, 2026
dsh plugin --profile web add dsh-pet-companionby Elefseus
Progress companion for the dsh web pet: folds durable todo/write and goal/change session events into the pet's status bubble, so it reports how much work is left - not just what the model is doing rig
★ 0
MIT
TypeScript
Oct 14, 2026
dsh plugin --profile web add dsh-pet-progressby ztyhehe
DSH(DeepSeek Harness)→ UniPet 桌面宠物联动插件:监听 turn/tool/approval/agent 生命周期事件,把思考中、执行工具、等待审批、失败、待检视等状态实时推给本地桌宠气泡,宠物不在线时自动拉起
★ 0
MIT
JavaScript
Aug 19, 2026
dsh plugin --profile web add unipet-dshby weekitmo
Completion and status notifications for DeepSeek Harness: desktop alerts, browser-tab unread counts, and sidebar session indicators, with per-event controls.
★ 0
MIT
TypeScript
Sep 8, 2026
dsh plugin --profile web add dsh-notifyby ldystudy0913-jpg
Desk pet for the DSH Web UI: an orange desk cat that reacts to the live agent session state.
★ 0
MIT
JavaScript
Aug 18, 2026
dsh plugin --profile web add dsh-pet