DeepSeek Harness plugin

dsh-pet-fetwtgra

A Codex/Shimeji-style desktop pet for DeepSeek Harness: a transparent, always-on-top, draggable companion that reacts to dsh's live work state.

Jump to install

Source facts

Repository
fetwtgrah/dsh-pet
Latest update
Aug 15, 2026
Category
Just for Fun
GitHub stars
0
Format
plugin
Catalog evidence
Upstream dsh.bundle evidence
Evidence path
package.json#dsh.bundle
Checked against
0.1.0-rc.8
Upstream check date
2026-08-20

This evidence comes from the upstream catalog. This site has not installed, run, or security-reviewed the plugin.

Install

Start with a prompt that asks an agent to review the GitHub repository and source. Switch to the command if you want to install it yourself.

Copy this prompt into DSH, Codex, or another agent and ask it to review the GitHub repository and source first.

Do not install or run any commands yet. Read this plugin's GitHub repository, README, and relevant source code. Then answer the questions below clearly and directly so I can decide whether it fits my needs:

1. What is this plugin, and what problem does it solve?
2. Who is it for, and what are its typical use cases?
3. How is it used after installation? Include one minimal example.
4. What known limitations or privacy, security, compatibility, or maintenance risks does it have?
5. Give a clear recommendation: recommend, conditionally recommend, or do not recommend, with reasons.

Distinguish statements documented by the repository, inferences from source code, and unknowns. If evidence is insufficient, say so explicitly. Do not guess or simply repeat the README.

GitHub: https://github.com/fetwtgrah/dsh-pet
Plugin: dsh-pet-fetwtgra
Author: fetwtgrah

Check the source files

Read the README and other files from this plugin directory before installing.

File explorer3 files
README.mdSource · read only

dsh-pet

一个 DeepSeek Harness桌面宠物 插件(Codex / Shimeji 风格):一个透明、置顶、可拖拽的小家伙,实时感知 dsh 的工作状态并切换动作与表情,让枯燥的终端 agent 变得有「陪伴感」。

  • 待机 idle —— 待在角落呼吸、偶尔眨眼/伸懒腰。
  • 忙碌 busy 及子状态 —— dsh 在跑工具调用时,按工具类型切换具体动作(读文件/写代码/跑命令/联网搜索/卡住/排队)。
  • 开心 happy —— 任务成功完成。
  • 沮丧 sad —— 出错/失败,弹一句简短错误摘要(不是完整堆栈)。
  • 打盹 sleep —— 长时间无活动自动入睡。

宠物是独立 Electron 进程detached 拉起),dsh 主机退出后它仍继续存活,直到你手动关闭——这是「陪伴感」的重点。

目录

  • [安装](#安装)
  • [使用](#使用)
  • [内置皮肤](#内置皮肤)
  • [皮肤格式](#皮肤格式)
  • [台词池](#台词池)
  • [busy 子状态与工具映射](#busy-子状态与工具映射)
  • [配置](#配置)
  • [架构](#架构)
  • [开发](#开发)
  • [常见问题](#常见问题)

安装

dsh plugin --profile web add <dsh-pet 的绝对路径>
# 或在本目录下:
dsh plugin --profile web add .

dsh plugin add 会转发给 profile 目录下的 pnpm,并自动把本插件(声明了 dsh.bundle.patch)注册进 bundle 列表。若 profile 正在运行,需重启一次让它加载新 bundle。

一次性:给宠物窗口装 Electron

Electron 刻意放进插件根目录的依赖里,这样 dsh plugin add 保持轻量(只依赖 ws)。装一次 Electron:

cd <path/to/dsh-pet>/pet-process
pnpm install          # 下载 Electron

pnpm 可能会要求你用 pnpm approve-builds 批准 Electron 的 post-install(二进制下载)——这是本插件唯一的、预期内的审批。也可以用已有的 Electron:

# 在 profile 的 cordis.patch.yml 里针对 dsh-pet 这一行覆盖:
- id: dsh-pet
  config:
    electronPath: 'C:/path/to/electron.exe'
# 或设置环境变量 DSH_PET_ELECTRON

使用

插件在所有 dsh 界面(Web / TUI / headless)注册了一个人类命令 pet

/pet start [--skin <名字|路径>]    启动桌面宠物
/pet stop                          停止桌面宠物
/pet status                        查看宠物进程状态
/pet help                          查看帮助

--skin 支持裸名字(自动解析到 skins/ 目录下,如 pixel-v1pixel-human-v1default)或绝对路径。

说明:

  • start 幂等——若已有存活 pid 会提示「已在运行」而不是重复拉起。
  • stop 可靠地杀掉整个 Electron 进程树(Windows:taskkill /T /F;POSIX:SIGTERM→SIGKILL),并清除 pid 文件。
  • 宠物在 dsh 主机退出后继续存活。主机重启后需 /pet stop/pet start 重新挂载到新主机。

右键菜单

  • 对话气泡 —— 开关气泡。
  • 静音(不打扰) —— 关掉宠物所有「碎嘴」(俏皮话 + 场景台词);错误摘要仍会显示。
  • 切换皮肤 —— 在 skins/ 下所有皮肤之间切换。
  • 打开设置 —— 打开持久化设置 JSON(位置、皮肤、开关)。
  • 退出 —— 退出宠物。

点击(不拖动)宠物会随机触发一句 idle_click 俏皮话。

内置皮肤

皮肤类型说明
skins/defaultemoji 占位最早的兜底皮肤,零图片资源
skins/pixel-v1程序化像素史莱姆64×64、描边 + 分色块阴影 + 抖动过渡,代码生成
skins/pixel-human-v1程序化像素小人戴兜帽的小码农/法师,有独立手臂腿,代码生成

三套皮肤都对齐同一套 11 状态状态机(idle / busy / reading / writing / executing / searching / stuck / happy / sad / queued / sleep)。

重新生成像素皮肤(零外部美术依赖,纯 Node 内置 zlib 写 PNG):

node scripts/generate-skin.mjs           # 重新生成 skins/pixel-v1
node scripts/generate-human-skin.mjs     # 重新生成 skins/pixel-human-v1

每个皮肤目录里都有:

  • manifest.json —— 皮肤描述与各状态帧数/fps;
  • preview.png —— 每行标注状态名的彩色预览图;
  • *-comparison.png —— 黑白剪影对比图(轮廓辨识度核对用)。

皮肤格式

皮肤 = 一个文件夹,含 manifest.json(以及可选的 sprite sheet + lines.json):

{
  "name": "my-pixel-pet",
  "version": 1,
  "size": { "width": 64, "height": 64 },   // 单帧像素尺寸
  "scale": 2,                              // 窗口放大倍数(像素画用 image-rendering: pixelated)
  "anchor": { "x": 0.5, "y": 1.0 },        // 脚底居中锚点(信息性)
  "emoji": "🐣",                           // 某状态无 sheet 时的 emoji 兜底
  "fps": 4,                                // 默认播放速率
  "states": {
    "idle":      { "sheet": "idle.png",      "frames": 6, "fps": 2 },
    "busy":      { "sheet": "busy.png",      "frames": 6, "fps": 6 },
    "reading":   { "sheet": "reading.png",   "frames": 5, "fps": 4 },
    "writing":   { "sheet": "writing.png",   "frames": 6, "fps": 7 },
    "executing": { "sheet": "executing.png", "frames": 6, "fps": 6 },
    "searching": { "sheet": "searching.png", "frames": 5, "fps": 4 },
    "stuck":     { "sheet": "stuck.png",     "frames": 5, "fps": 3 },
    "happy":     { "sheet": "happy.png",     "frames": 6, "fps": 8 },
    "sad":       { "sheet": "sad.png",       "frames": 4, "fps": 3 },
    "queued":    { "sheet": "queued.png",    "frames": 4, "fps": 3 },
    "sleep":     { "sheet": "sleep.png",     "frames": 4, "fps": 1 }
  }
}
  • sheet 是相对皮肤目录的图片路径;帧水平排列,每帧 size.width × size.height;实际显示尺寸为 size × scale
  • 某状态没有 sheet(或图片缺失)时回退到 emoji + CSS 动画——default 皮肤就是这么零资源工作的。
  • 皮肤可自带 lines.json 覆盖台词池;否则使用 pet-process/renderer/lines.json

台词池

lines.json 是「场景 → 台词数组」的对象,每个触发点随机抽一句,纯装饰、不接 LLM:

场景触发时机
idle_click点击宠物
task_start任务开始(进入忙碌)
task_success任务成功(happy)
task_fail任务失败(sad,与错误摘要合并显示)
idle_too_long空闲约 60% 时长,每次空闲段触发一次
sleep_wake从 sleep 醒来
random_ambient空闲时每随机 3–8 分钟自动弹一句

旧的扁平字符串数组格式仍兼容,会被包成 idle_click

busy 子状态与工具映射

busy 被拆成子状态,映射到实际调用的工具。映射表在 tool-behavior-map.json(独立于代码,加/删工具名无需重新构建):

{
  "reading":   ["read", "read_image", "glob", "grep", "ssh_list"],
  "writing":   ["write", "edit", "str_replace_editor"],
  "executing": ["bash", "pwsh", "ssh_exec", "ssh_cluster", "ssh_upload", "ssh_download", "ssh_tunnel"],
  "searching": ["web_search", "web_fetch"]
}
子状态触发说明
reading读文件/搜索代码类工具翻书/扫视
writing写文件/编辑类工具打字
executingshell / ssh 类工具抡锤/举臂
searching联网搜索/抓取类工具举天线
stuck某工具调用超过 stuckThresholdMs(默认 20s)仍未返回抓头
busy未映射的陌生工具通用忙碌兜底
  • 宿主侧状态机用「在飞调用栈」跟踪:最新调用驱动当前子状态;顶调用超时则切 stuck,调用结束后回到 happy/sad。
  • queued 未接线:dsh 事件总线没有暴露工具调度队列长度,没有诚实的触发信号,所以不做伪造数据触发。queued 的 sprite sheet 已生成,等以后有队列信号即可启用。

配置

配置都在 dsh-pet 这一行(见 cordis.patch.yml),可在 profile 自己的 cordis.patch.yml 里按 id 覆盖:

key默认含义
idleTimeoutMs600000无活动多久后打盹
stuckThresholdMs20000工具调用超过该时长切 stuck
bubbleOnErrortrue失败时显示错误摘要气泡
electronPathElectron 二进制绝对路径
skin内置 skins/default默认皮肤目录

架构

dsh 主机(本插件)                            Electron 宠物(detached)
┌──────────────────────────────┐  WebSocket  ┌──────────────────────────────┐
│ src/index.ts  apply(ctx)      │(回环随机端口│ pet-process/main.js          │
│   ├─ /pet start|stop|status   │  + token)  │   ├─ 透明置顶窗口             │
│   ├─ tools/execute → busy 子状态│ ─────────► │   ├─ WS 客户端(断线重连)     │
│   ├─ tools/result  → happy/sad│            │   ├─ 原生右键菜单             │
│   ├─ jobs.onJobDone → happy/sad│            │   └─ 光标轮询式鼠标穿透/拖拽    │
│   └─ jobs.onJobsChanged → busy │            │ renderer/                    │
│ src/state.ts  情绪状态机(子状态+stuck)      │   ├─ skin-loader.js(精灵表)  │
│ src/ipc.ts    WS 服务端                    │   └─ pet.js(情绪/气泡/台词)    │
└──────────────────────────────┘            └──────────────────────────────┘

用到的状态信号(已对照实际发布的包核实,非臆测):

信号来源含义
tools/execute(waterfall)@deepseek-ai/dsh-tools工具开始 → 按工具名切 busy 子状态
tools/result(emit)@deepseek-ai/dsh-tools工具结束 → happy / sad
ctx.jobs.onJobDone@deepseek-ai/dsh-jobs后台 job 结束 → happy / sad
ctx.jobs.onJobsChanged@deepseek-ai/dsh-jobsjob 注册表变化 → 有界 busy 脉冲

进程生命周期:pid/port/token 记录在 $DSH_HOME/dsh-pet.json(缺省 ~/.dsh)。start 复用存活 pid;stop 杀掉记录的 pid 并清文件。Electron 窗口的位置/皮肤/开关存在它自己的 userData 目录。

开发

# 宿主侧:TypeScript 源码在 src/,发布入口是已编译的 lib/
pnpm install                          # 装 dev 依赖(typescript、@types/*)
pnpm build                            # tsc: src/*.ts -> lib/*.js
node scripts/smoke-lib.mjs            # 冒烟测试(状态机 + IPC + 接线,跑编译后的 lib)

# 像素皮肤(零依赖,纯 Node zlib PNG 编码器)
node scripts/generate-skin.mjs        # 重新生成 skins/pixel-v1
node scripts/generate-human-skin.mjs  # 重新生成 skins/pixel-human-v1
node scripts/generate-silhouette-comparison.mjs   # 史莱姆剪影对比(旧脚本)
node scripts/ascii-preview.mjs <state>            # 把生成的帧转成字符画核对
node scripts/frame-diff.mjs           # 逐帧像素差异,检查动画是否真的在动

# 宠物侧(需要 pet-process 里装好 Electron)
cd pet-process && pnpm start                        # 单独跑窗口
node scripts/pet-demo-substates.mjs --skin pixel-human-v1   # 演示全部 11 状态(--skin <名字>)
node scripts/pet-demo.mjs pixel-v1                  # 演示 5 种基础情绪(位置参数皮肤名)
node scripts/pet-boot-test.mjs pixel-v1             # 对 mock 宿主做启动/握手测试(位置参数皮肤名)
node scripts/pet-restart.mjs pixel-v1               # 原地重启真实宠物并切换皮肤(位置参数皮肤名)
node scripts/pet-observe.mjs                        # 直连宿主抓取真实状态帧(验证分类)

lib/ 已提交入库,是 dsh plugin add 实际加载的入口;只有改了 src/*.ts 才需要重新 pnpm build

常见问题

  • 装 Electron 卡在 approve-buildspnpm approve-builds 批准 electron,或改用 npm install(npm 默认执行 postinstall),或用 electronPath / DSH_PET_ELECTRON 指向已有 Electron。
  • 切皮肤后还是老样子:先 /pet stop/pet start --skin <名字>start 对已运行的宠物不会热切换。
  • 路径含中文/空格:皮肤路径会自动做 file:// 百分号编码,正常解析;--skin 用裸名字最省事。
  • 改了代码要生效src/*.ts 改完需 pnpm build 并重启 dsh 主机;pet-process/skins/ 改完只需重启宠物(pet-restart.mjs/pet stop + /pet start)。

License

[MIT](./LICENSE)