Desktop pet drawn from the official whale outline, for both the desktop app and the web UI: SVG animations and facial expressions follow agent state (thinking, working, done, error, waiting for confirmation), and it can follow other running sessions with a count badge; poke it, drag it, shake it dizzy, squash it against a screen edge, or rub the cursor over its head for a headpat; 7 palettes, 4 sizes, synthesized sounds, light/dark theme sync, and a playable web preview.
Install
# from npm (prebuilt)
dsh plugin --profile web add pet-whale
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:nzl153/dsh-pet-whale
Any plugin you install runs third-party code with your own permissions — it can read your files, use your credentials, and reach the network, and tool approvals don’t sandbox it. GitHub-sourced plugins also run build scripts at install time — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
English | 中文
A desktop pet plugin for DeepSeek Harness (DSH), works in both the desktop app and the web UI. A small whale floats in the bottom-right corner and reacts to your agent's state in real time.
It uses the official DeepSeek whale outline, pure DOM animations, zero runtime third-party dependencies, and WebAudio-synthesized sound effects (no audio files).
Live Preview
Open the standalone preview page to try every state and interaction:
👉 https://nzl153.github.io/dsh-pet-whale/preview.html
preview.html loads the real plugin (lib/client.js) and feeds it state from a few buttons instead of DSH; serve the repo root with any static server to run it locally.
Install
In the profile directory you are using (the desktop app and the web UI each have their own; for the web UI it is usually ~/.dsh/profiles/web), add the dependency and the bundle to package.json:
{
"dependencies": { "pet-whale": "^1.2.5" },
"dsh": { "profile": { "bundles": ["...existing...", "pet-whale"] } }
}
Then run pnpm install in that directory and restart DSH. For the desktop app, quit from the tray and reopen it.
Without npm:
# Local directory install (the repo already contains built lib/, no build needed)
dsh plugin --profile web add link:/path/to/pet-whale
# Or install directly from Git
dsh plugin --profile web add "github:nzl153/dsh-pet-whale#main"
Requires DSH >=0.1.5-alpha.2 <0.3.0 and Node.js ^22.19.0 || >=24.0.0. See Compatibility for how each release was checked.
Features
State & companionship
| Capability | Description |
|---|---|
| State machine | idle / think (diving) / working (swimming + typing + code particles) / celebrate (leaping + bubbles) / error (shaking + black lines) / disappointed (a brief slump after an error) / wait (waiting for your input); priority: error > celebrate > think > working > idle |
| Expressions | The eyes follow the mood: squinted ^ when celebrating, drooping with a worried brow and a falling tear when disappointed, crosses on error, crescents when sleeping or belly up, an angry brow when sulking, >_< when grabbed |
| Turn semantics | Never idle while a turn is running: with tools = working, without tools (text or internal reasoning) = think diving; keyboard animation sticks for 2.5s during tool-heavy phases |
| Think ticker | While thinking, the latest reasoning text scrolls above the whale (can be toggled) |
| Follow all sessions | DSH 0.1.7+: when the current session is idle but another one is running, the whale keeps working and a little bubble badge in front of its head shows how many are running (it rides the body's bobbing, sways gently, and pops when the number changes); when another session finishes it celebrates and names it, and it tells you which session is waiting for confirmation. Subagents don't count. Can be turned off under "More settings → Behavior" |
| Parallel chatter | With follow-all on: at 2 sessions running at once (current included) it cheers about the parallel push, at 4 it starts complaining about overtime; each level is announced once, and only when a batch that really ran in parallel is fully done does it sigh "so tired... finally got every last one done" |
| Error care | Click the whale during error state to copy the error text |
| Comforting | Poking during the post-error disappointed state counts as comfort: a happy animation, a warm line, and the sulk ends early |
| Bond level | Interactions, completed turns and days together all feed one score, split into three tiers: Acquainted / Close / Inseparable. The tier changes poke lines and greetings, and at Inseparable the whale occasionally speaks up on its own; it announces each promotion, and the current tier and progress show up under Companion Stats |
| Idle micro-movements | Random swimming, looking around, and bubble blowing |
| Free swimming | Toggle "Swim" to let the whale roam the page along cubic Bezier paths, with banking, adaptive flipping, depth dives, wake ripples, and splashes; it yields while the agent is busy, and the preference is persisted |
Interactions
| Capability | Description |
|---|---|
| Basics | Click to poke, double-click 360° flip, drag with >_< eyes, right-click quick menu, mouse-follow eyes, 20s idle sleep |
| Headpat | No buttons needed: rub the cursor back and forth over its head and the cursor turns into a little hand. From the second stroke it squints and blushes, each stroke presses its head down a little, and every five strokes a heart floats up. Keep rubbing frantically (eight strokes in about a second) and it frowns, grumbles about going bald, swims off, and refuses pats for five seconds. Patting it while disappointed counts as comfort. The right-click "Headpat" and a 0.7s long press play the same reaction |
| Poke escalation | Pokes 1-2 get the usual squish, 3-5 make it lean away annoyed, 6+ turn it away with an angry brow; the streak decays after 2.6s of no poking, and it drops the mood the moment the agent starts working |
| Shake dizzy | Grab it and swing it left and right - four direction changes within a second make it dizzy: the eyes go @@, it begs you to stop, and the body keeps wobbling after you let go; hand tremor and slow back-and-forth do not count. It stays woozy for the next dozen seconds too, with a visibly crooked swim path |
| Belly up | The double-click reaction follows your bond: normally a 360 spin, but at Inseparable it rolls over and shows you its belly |
| Edge squash | Push it against a screen edge and it flattens: narrower against the left and right edges, a whale pancake against the top and bottom, squashing from the edge it is pressed on; in a corner only left/right counts. It springs back once you move away |
| Held too long | Hold it in midair for over 2 seconds and it starts squirming to ask if you are still there; moving stops the nagging |
| Smart avoidance | In idle, the whale moves aside when the cursor lingers nearby; grabbing/right-click cancels and cools down for 8s |
| Capability | Description |
|---|---|
| Finish alert | When a turn completes while you are on another tab, the tab title becomes "✅ Done · " and restores when you come back; an optional system notification is off by default and only asks for permission when you enable it |
| Break reminder | Set 45 / 60 / 90 minutes and the whale surfaces with a spout to nudge you; off by default, and leaving the page for over 10 minutes counts as a rest and resets the timer |
| Scheduled hide | Hide after 1 hour or every day at 22:00 |
| Capability | Description |
|---|---|
| Skins | 7 built-in palettes (default Theme Blue), extensible by adding one line in src/client/palettes.ts |
| Custom size | Right-click - Appearance - Size cycles through Small / Standard / Large / Huge (0.8x - 1.6x). Scaling also moves the collision bounds, splash origins and edge detection, not just the visual |
| Sound | WebAudio-synthesized sounds, can be muted; "More settings → Behavior → Volume" cycles Muted / Low / Medium / High, remembered across reloads |
| Pretend work | "Pretend to work" mode keeps the typing animation on; preference is persisted |
| Hide/recall | Hide to a small 🐳 button; state persists across refresh |
| Quick menu & settings | Right-click opens a compact quick menu; "More settings" opens a grouped panel (appearance / behavior / stats) |
| Stats | The settings panel keeps running counts of completions, interactions, errors, and days spent together |
| Localization | Full Chinese and English copy, following the DSH locale service automatically; the standalone preview page detects the browser language and can be overridden manually |
| Theme sync | Follows DSH light/dark theme for bubbles, dialogs, and shadows |
| Background power saving | Animations, sounds, and the think ticker pause when the page is hidden |
| Accessibility | Respects prefers-reduced-motion |
Community forks
The whale itself is maintenance-first (see CONTRIBUTING.md). If you want more, take a look at these forks:
- wzwei1990/dsh-pet-whale — multi-pet architecture
(switch between the whale, a cat and Ling'er, each with its own SVG, animations and lines),
plus a
pnpm pet:doctorhealth-check script
These forks are maintained independently and are not affiliated with this repository. Please read their own documentation before using them.
Development
Build & Test
pnpm install
pnpm typecheck # TypeScript type check
pnpm dev # dev watch: rebuild lib/client.js on src/client changes; DSH HMR applies it automatically
pnpm build # tsdown → lib/index.mjs + lib/client.js
pnpm test # build first, then run smoke and regression tests
pnpm verify # type check, build and test, then verify the npm package as a consumer
pnpm verify:hmr # verify local repo <-> running DSH HMR wiring
src/client/palettes.ts— palette extension point. Add one line for a new skin.scripts/verify-live.mjs— one-click live verification after restart.- State source (dsh 0.1.5): session lifecycle comes from the
ctx.sessionssnapshot (running/lastAgentError/openError);partial/runningCalls/turnEndscome from the chat projection onctx.uiConversation(ChatSnapshot.legacy). The field-by-field mapping lives at the top ofsrc/client/state.ts. - 0.1.7 changes: the current session is the list row with
retainedBy.mainView > 0(the list no longer hascurrent); per-session running / pending confirmation comes fromctx.uiSession.sessionStatusand drives the wait state and follow-all-sessions. 0.1.5 has nosessionStatus, so both are simply inactive there. - patches/ holds early personal UI patches written for DSH rc.6. They are not part of this plugin, no longer maintained, and kept for reference only.
Hot Reload
Prerequisite: the DSH profile installs this repo via link: (not a static GitHub/npm copy), and DSH >=0.1.5-alpha.2.
pnpm dev watches src/ and rebuilds lib/client.js (and the host half lib/index.mjs).
The built-in @deepseek-ai/dsh-client-hmr polls for rebuilds and broadcasts through /plugins/events;
the browser invalidates and reloads this plugin automatically. Client-only changes do not require a DSH restart or a page refresh.
- Hot-reloadable:
src/client/**UI, styles, state machine, interactions. - Still requires a DSH restart:
- changes to
package.jsondsh.client/dsh.bundle/exportsstructure cordis.patch.ymlor host halfsrc/index.mjscomposition changes- installing/uninstalling dependencies, upgrading DSH, changing the profile bundle list
- changes to
- Verify wiring with DSH running:
pnpm verify:hmr.
Compatibility
| DSH release | Status | Basis |
|---|---|---|
0.2.0-rc.1 |
compatible | Ran on a real host (official desktop app): tool call idle → working → celebrate → idle all fired. The session list, session snapshot, chat projection and sessionStatus types this plugin reads are the same as 0.1.7-rc.2 |
0.1.7-rc.2 |
compatible | Ran on a real host: tool call idle → think → working; switching to another session mid-turn shows badge 1 without a false celebrate; when the background session finishes it celebrates, names the session, and falls back after 2.5s; zero console errors. Also in daily use on the official desktop app preview |
0.1.7-rc.1 |
compatible | Not run. npm type comparison: the only differences from rc.2 are in the model-catalog interfaces; the session list, session snapshot, chat projection and sessionStatus this plugin reads are unchanged |
0.1.5-rc.3 |
compatible | Not run. Only pins dependency versions relative to rc.2 |
0.1.5-rc.2 |
compatible | Ran on a real host: new session, tool call, plain-text turn — zero console errors, idle → think → working → celebrate → idle all fired, and celebrate self-expires back to idle after 2.5s (no next message needed to unstick it) |
0.1.5-rc.1 |
compatible | Not run. Per-file npm comparison: dsh-api-session-controller, dsh-client-ui-conversation, dsh-client-locale, dsh-client-modules, dsh-cordis-client-runner are byte-identical to rc.2; dsh-client-ui-chat differs only by a 15-character CSS tweak unrelated to the legacy projection this plugin reads |
0.1.5-alpha.2 |
compatible | Not run. Same as above; the only difference is a doc-string line number (contract.ts:23 → :24) in dsh-cordis-client-runner |
The previous 1.1.0 threw TypeError: Cannot read properties of undefined (reading 'length') on every session-snapshot
update under 0.1.5. The field-by-field mapping and the fix are documented at the top of src/client/state.ts.
1.1.2 doesn't throw under 0.1.7 but stays idle forever: 0.1.7 removed current from the session list, so the plugin
couldn't find the current session. Since 1.1.3 it uses the session held by the main view.
License
Links
More in this category
PC2005-cloud/dsh-pet#dsh-pet★ 883
Desktop pet for the DSH Web UI with 25 transparent animations, screen wandering, click reactions and drag, plus a reproducible asset-generation pipeline.
Nagi-ovo/dsh-ads★ 639
Parody ads in 2005-Chinese-web style: sidebar banners, in-chat feeds, corner popups, and a close button whose hit area is smaller than it looks. All fictional.
vlln/whale-girl★ 340
Desktop pet (QQ-pet style): floats in the corner, draggable, feedable, playable.
a86582751/dsh-nexttavern★ 122
Roleplay workspace for DeepSeek Harness: import SillyTavern/TauriTavern character cards or author one from scratch through an interactive flow; turn a long TXT novel into a playable card with either an intensive or a coarse reading pass; the main agent consults worldbooks on demand, and prose is read in a dedicated tavern reader tab; explore multiple worldlines inside one conversation (regenerate, edit-and-send, explicit clone); scoped style presets with 16 built-in styles, a character agent cluster, a standalone decision card, and card or novel export; long stories hold together through a frozen settings prefix, a hard-cut context window, traceable director notes and keyword/semantic/hybrid recall, with embeddings either online or fully local. Targets Harness 0.1.2-alpha.3 and applies its documented compatibility patches.
yyh-001/dsh-meme★ 115
Chat meme stickers: text-only send, mood auto-send, QQ/WeChat-style picker, auto-learn, custom packs.
Sutera-Diffusus/dsh-whale-musume★ 96
A whale-girl desktop pet for the DeepSeek Harness Web UI: pat-to-raise growth, work-state poses per tool type, 494 dialogue lines, 30 achievements, a growth diary, drag physics, theme sync and an accessibility mode. Chat-keyword sensing and API-balance care ship off and are opt-in; an optional weather mood calls Open-Meteo only after you name a city. No telemetry.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.