dsh-pet
PC2005-cloud/dsh-petDSH Web UI 桌面宠物,Windows / macOS / Linux 三端可用:浏览器浮层或透明置顶桌面窗口,106 个透明动画、带挤压与粒子的甩抛反弹物理、随会话事件实时联动的工作状态气泡、碎碎念与带持久记忆的对话、余额展示、表情包配图、多开同屏,并支持直接放入的自定义动画与宠物种类(pet pack)。
English | 中文
A registry-driven desktop companion for DeepSeek Harness — the built-in whale girl plus any pet you drop in.
While the model thinks, you wait — your pet swims. It follows official session activity and switches animations while waiting, thinking, using tools, composing a reply, celebrating completion, or reporting failure; you can also pat its head, feed it dried fish, and watch its affinity grow. Pets are registry entries, not code: every pet is one pet.json manifest plus one atlas image, and the host discovers them at startup.
Re-implemented from the pet feature of the Codex desktop app, as an official DSH plugin shape (cordis bundle: host half + client half in one package).
| Feature | Description |
|---|---|
| Multi-pet registry | The host scans built-in assets/, the hatch-pet custom pets directory, and composed config entries; each pet is a manifest plus an atlas |
| Pet selection in settings | The plugin settings card lists every registered pet; switching persists and the sprite swaps immediately |
| Per-pet naming | Rename from the hover panel; each pet keeps its own name (stored per pet id, migrated from the legacy flat name) |
| State animation | Official session activity → 9-state animation: thinking → running, tool → running-right, review → review, waiting → waiting, done → jumping, failed → failed |
| Head-pat interaction | Click the pet → bubble feedback + affinity +1 (10s cooldown) |
| Feeding | Hover panel 喂食 (Feed) → consumes 1 dried fish + affinity +5 (30s cooldown) |
| Treat economy | Dried-fish stock (cap 20): +1 every 3 rounds of work, +1 every 30 minutes |
| Affinity | +1 per round completed; 4 levels: 幼鲸 → 伙伴 → 挚友 → 深海羁绊 (capped at 100) |
| Dragging | Hold and drag the pet to reposition; position persisted |
| Hide/Summon | Hover panel 隐藏 (Hide); after hiding, a 召唤{name} (Summon {name}) button appears |
| Status bubble | Shows the current session stage or tool name; transient interaction feedback temporarily takes priority |
| Multi-session activity | The pet is host-global: the most recent meaningful event controls its display, while completed turns from every session contribute affinity and treats |
A pet is a directory holding one pet.json manifest and one atlas image. Nothing else is required — no host or client code changes.
{
"id": "whale-girl", // unique lowercase kebab id
"displayName": "鲸鱼娘", // shown in the settings selector and panel
"description": "A soft healing whale-girl.", // optional
"spritesheetPath": "spritesheet.webp", // atlas, relative to the manifest
"cell": { "width": 192, "height": 208 }, // optional; defaults to the Codex contract
"columns": 8, // optional; default 8
"frames": [6, 8, 8, 4, 5, 8, 6, 6, 6], // optional per-row frame counts
"tracks": { // optional per-track rhythm overrides
"idle": { "durations": [400, 400, 500, 400, 400, 500] }
}
}
frames counts the used columns per row (defaults to the hatch-pet contract table [6, 8, 8, 4, 5, 8, 6, 6, 6]); tracks overrides per-frame durations (cycled to the row's frame count), loop, and fallback per animation (defaults: everything loops; jumping and failed hold their last frame, then fall back to idle).Where pets come from (later sources override earlier ones on id collision):
assets/<dir>/pet.json in this package.${CODEX_HOME:-~/.codex}/pets/<pet>/pet.json — the hatch-pet pipeline stages its output there, so a hatched pet appears in the selector with no further wiring.PetConfig.pets manifest entries passed to the plugin by the embedding application.The registry is built once at host startup; add or change a pet, then restart dsh web.
The sprites are an 8-column × 9-row atlas (192×208 cells) generated by the hatch-pet pipeline; below are previews of each state:
| idle | waiting | running | jumping |
|---|---|---|---|
| waving | review | failed | move left/right |
|---|---|---|---|
dsh-pet/
|-- src/
| |-- index.ts # host half: plugin entry (registry build, settings section, routes)
| |-- registry.ts # multi-pet contract: manifest scan + normalization (assets + custom pets)
| |-- service.ts # PetService: pet selection + state machine + affinity + config
| |-- state.ts # pet state machine: projected session activity → 9 state animations
| |-- affinity.ts # affinity ledger (pure functions + cooldowns)
| |-- treats.ts # dried-fish stock ledger
| |-- persist.ts # persistence ($DSH_HOME/pet.json: selection + per-pet names, atomic write)
| |-- routes.ts # /api/pet/* JSON API + /pet/<id>/* asset routes
| `-- client/ # browser half
| |-- index.ts # global mount (createRoot → body) + registry fetch + polling + wiring
| |-- PetDockEntry.tsx # global floating entry (document.body, always shown)
| |-- PetSprite.tsx # definition-driven floating sprite (portal + rAF + dragging)
| |-- PetSettingsCard.tsx # settings card: pet selector + display layout
| |-- spritesheet.ts # atlas geometry helpers + track trimming
| `-- pet.module.css
|-- assets/whale/ # built-in whale-girl (pet.json + spritesheet.webp + previews)
`-- cordis.patch.yml # bundle patch: inserts the pet plugin row
official session events (turn/step/chunk/tool) ----\
> PetService (host) <-- registry (assets + custom pets)
optional legacy activity/status ------------------/
| /api/pet/* JSON
global React root (createRoot → document.body) <-- polling 2s -- pet-client (browser)
|
PetSprite floating layer (portal + rAF)
turn/start, step/start, assistant/chunk, assistant/message, tool/call, tool/result, and turn/end events into waiting/thinking/tool/review/done/failed states. Optional legacy activity/status events remain a compatibility input./api/pet/pets; the browser half renders any entry from that definition and carries no per-pet code.petId lives in the settings namespace; per-pet names live in pet.json under names, edited through the hover-panel rename of the active pet. Legacy installs migrate their flat name onto the whale girl.document.body (global React root, always shown: no session / new session / mid-session — the old mount point conversation.composer.dock only rendered in an active session, hiding the pet in new sessions); the component uses createPortal internally to render the global floating layer./api/pet/* JSON endpoints (state/pets/interact/set-visible/set-config/set-name/set-pet); each pet's atlas loads from /pet/<id>/<spritesheetPath> — the plugin self-sufficiently provides its own API and assets (the same pattern as dsh-remote-web-ui's /api/pair).Install the family aggregate package @linxin666/dsh-web-ui-all (all plugins and skins in one) or this plugin alone:
### From npm (recommended)
dsh plugin --profile web add @linxin666/dsh-pet
### From the repository (development)
git clone https://github.com/zhu1090093659/dsh-web-ui.git
cd dsh-web-ui
pnpm install && pnpm -r build
dsh plugin --profile web add link:$(pwd)/packages/dsh-pet
After installing, restart dsh web — your selected pet appears at the bottom-right of the interface. In link mode, pnpm build and refresh the page after a code change; no reinstall needed.
pnpm build # tsc -b (types+declarations) && tsdown (node half + browser bundle)
pnpm test # vitest unit/component tests (registry / event projection / state / UI / ledgers)
pnpm prepare # transpile-only build (no type checking, for consumer installs)
pnpm typecheck # type check only
The browser bundle rides the window.__ModuleLoader__.load contract; React/cordis and so on resolve from the loader's module table (external); CSS Modules are inlined by lightningcss as <style data-plugin>.
The built-in whale-girl atlas is generated by the hatch-pet pipeline as 9 states × 8 columns: assets/whale/spritesheet.webp (1536×1872, 8 columns × 9 rows of 192×208 cells) + assets/whale/pet.json. The frame count and rhythm of each row live in that manifest's frames and tracks fields — the whale girl carries its own slower healing durations, while pets without overrides follow the hatch-pet contract rhythm. Redoing artwork therefore only edits assets/whale/pet.json (row-order contract: 0 idle / 1 running-right / 2 running-left / 3 waving / 4 jumping / 5 failed / 6 waiting / 7 running / 8 review).