@neystan/dsh-pet
v0.1.16
Published
鲸鱼娘宠物插件 for the dsh web GUI: a soft healing whale-girl companion with model-activity animations, petting/feeding interactions, shared affinity, and one validated custom-pet import slot
Readme
dsh-pet — Whale-girl pet plugin
English | 中文
A soft, healing whale-girl who works alongside you in DeepSeek Harness.
While the model thinks, you wait — she swims. She follows official session activity and switches animations while waiting, thinking, using tools, composing a reply, celebrating completion, or reporting failure; you can also pat her head, feed her dried fish, and watch her grow from a baby whale into your deep-sea bond.
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).
Features
| Feature | Description |
|---|---|
| State animation | Official session activity → whale-girl animation: thinking → running, tool → running-right, review → review, waiting → waiting, done → jumping, failed → failed |
| Head-pat interaction | Click the whale-girl → 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; when low it prompts "多陪鲸鱼娘工作一会儿" (Work with the whale-girl a bit more) |
| Affinity | +1 per round completed; 4 levels: 幼鲸 (baby whale) → 伙伴 (companion) → 挚友 (close friend) → 深海羁绊 (deep-sea bond, capped at 100) |
| Custom naming | Hover panel "改名" (Rename) → 1–20 characters, persisted, echoed in the summon button/panel |
| Dragging | Hold and drag the whale-girl to reposition; position persisted |
| Hide/Summon | Hover panel "隐藏" (Hide); after hiding, a "召唤{name}" (Summon {name}) button appears in the input selector row |
| 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 |
| Custom pet | Select pet.json and spritesheet.webp separately in Settings, validate and preview a candidate, then switch without a page refresh; the official and custom cards share the name, affinity, treats, size, and position |
Custom pet (MVP)
Settings → Pet → Pet appearance shows the official whale girl and one custom-pet card. The custom pair must use the existing asset contract:
pet.json:id,displayName,description,spritesheetPath: "spritesheet.webp", and fixed frame counts[6, 8, 8, 4, 5, 8, 6, 6, 6].spritesheet.webp: transparent WebP,1536×1872, 8 columns × 9 rows,192×208cells; rows areidle,running-right,running-left,waving,jumping,failed,waiting,running,review.
Choose the two files separately. The browser checks dimensions, transparent pixels, valid frames, and empty tail cells. Import creates a “Pending apply” preview; “Use selected pet” performs the switch without refreshing the page. Custom files live in the single current/candidate layout under $DSH_HOME/pet/custom. Switching back to the official pet or deleting custom files keeps the shared progression data.
Use “Copy generation prompt” to give an Agent the prompt together with one clear, full-body, unobstructed pet reference image. The prompt uses only the repository-relative template path packages/dsh-pet/assets/whale and asks for exactly pet.json and spritesheet.webp. The first version does not call AI, convert still images, accept archives, or manage multiple custom pets.
Animation preview
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 |
|---|---|---|---|
|
|
|
|
|
Architecture
dsh-pet/
|-- src/
| |-- index.ts # host half: plugin entry (cordis apply, route registration)
| |-- service.ts # PetService: pet state machine + affinity + config (HTTP API service face)
| |-- 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, atomic write)
| |-- routes.ts # /api/pet/* JSON API + /pet/whale/* static asset routes
| `-- client/ # browser half
| |-- index.ts # global mount (createRoot → body) + polling (800ms) + interaction wiring (fetch)
| |-- PetDockEntry.tsx # global floating entry (document.body, always shown: no session / new session / mid-session)
| |-- WhalePet.tsx # floating component (portal + rAF frame animation + dragging)
| |-- spritesheet.ts # atlas geometry + per-state animation tracks (frames/duration)
| `-- pet.module.css
|-- assets/whale/ # whale-girl assets (pet.json + spritesheet.webp + animation previews)
`-- cordis.patch.yml # bundle patch: inserts the pet plugin rowData flow
official session events (turn/step/chunk/tool) ----\
> PetService (host)
optional legacy activity/status ------------------/
| /api/pet/* JSON
global React root (createRoot → document.body) <-- polling 800ms -- pet-client (browser)
|
WhalePet floating layer (portal + rAF)- Status source: the host projects official
turn/start,step/start,assistant/chunk,assistant/message,tool/call,tool/result, andturn/endevents into waiting/thinking/tool/review/done/failed states. Optional legacyactivity/statusevents remain a compatibility input. - Multi-session semantics: the API and browser mount are host-global and expose no foreground-session identity, so the most recent meaningful event wins the display. Every session's completed turns are still rewarded independently, and disposing a non-current session does not reset the visible state.
- Mount point:
document.body(global React root, always shown: no session / new session / mid-session — the old mount pointconversation.composer.dockonly rendered in an active session, hiding the pet in new sessions); the component usescreatePortalinternally to render the global floating layer. - Rendering: CSS sprite (background-position) per-frame animation, frame durations from the track definitions in
spritesheet.ts. - Communication: browser ↔ host over the same-origin
/api/pet/*JSON endpoints (state/interact/set-visible/set-config); the atlas loads from/pet/whale/spritesheet.webp— both the RPC domain and the/plugins/static service are platform-registered, and the plugin self-sufficiently provides its own API and assets (the same pattern as the family RPC domains).
Install
Install the family aggregate package @neystan/dsh-web-ui-all (all plugins and skins in one) or this plugin alone:
### Install from npm (recommended)
dsh plugin --profile web add @neystan/dsh-pet
### 从仓库安装(开发调试)
git clone https://github.com/neystan/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 — the whale-girl appears at the bottom-right of the interface. In link mode, pnpm build and refresh the page after a code change; no reinstall needed.
Development
pnpm build # tsc -b (types+declarations) && tsdown (node half + browser bundle)
pnpm test # vitest unit/component tests (event projection / state / UI / ledgers)
pnpm prepare # transpile-only build (no type checking, for consumer installs)
pnpm typecheck # type check onlyThe 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>.
Sprites and animation-track calibration
The 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 actual frame count and rhythm of each row are defined in TRACKS in src/client/spritesheet.ts. If the artwork is redone and the frame count changes, only that table needs updating (row-order contract: 0 idle / 1 running-right / 2 running-left / 3 waving / 4 jumping / 5 failed / 6 waiting / 7 running / 8 review).
