Multi-pet companion for the dsh web GUI: a registry-driven floating pet that reacts to model activity.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:DamonKoy/dsh-web-ui#path:/packages/dsh-pet
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. Only install sources you trust, and pin a commit (github:owner/repo#sha).
README
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).
Features
| 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 |
Pet contract
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] }
}
}
- The atlas is an 8-column × 9-row grid (192×208 cells by default); rows are fixed in this order: 0 idle, 1 running-right, 2 running-left, 3 waving, 4 jumping, 5 failed, 6 waiting, 7 running, 8 review. Unused cells stay fully transparent.
framescounts the used columns per row (defaults to the hatch-pet contract table[6, 8, 8, 4, 5, 8, 6, 6, 6]);tracksoverrides per-frame durations (cycled to the row's frame count),loop, andfallbackper animation (defaults: everything loops;jumpingandfailedhold their last frame, then fall back toidle).
Where pets come from (later sources override earlier ones on id collision):
- Built-in:
assets/<dir>/pet.jsonin this package. - Custom pets:
${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. - Composed:
PetConfig.petsmanifest 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.
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 (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
Data flow
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)
- 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. - Registry: the host normalizes every manifest into a full render definition (geometry, per-row frame counts, per-track durations) and serves it over
/api/pet/pets; the browser half renders any entry from that definition and carries no per-pet code. - Selection & naming:
petIdlives in the settings namespace; per-pet names live inpet.jsonundernames, edited through the hover-panel rename of the active pet. Legacy installs migrate their flatnameonto the whale girl. - 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 come from the served definition's tracks.
- Communication: browser ↔ host over the same-origin
/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
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.
Development
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>.
Sprites and animation-track calibration
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).
License
Links
More in this category
zhu1090093659/dsh-web-ui#packages/dsh-web-ui-all★ 3649
Plugin and skin collection for the DSH Web UI: task board, Git graph, right-side panel, remote mobile UI, pet, live token stats, and a skin center.
omdsh-dev/DSH-better-sidebar★ 1731
Full sidebar workbench with file rendering and editing, terminal, Git, and subagents; third-party plugins can register new tabs.
ccch1mneyyy/dsh-TUI★ 1646
Claude Code-style full-screen terminal UI: pixel-whale header, live status line, and streaming thought expansion.
omdsh-dev/dsh-at-file★ 288
Codex-style `@file` mentions: search workspace files in the composer and attach their contents to prompts.
huiliyi37/dsh-tianshu-tui★ 194
A terminal UI (TUI) for DeepSeek Harness.
Nagi-ovo/dsh-visualize★ 161
In-conversation generative UI: the model renders interactive HTML cards into the chat stream, with streaming preview and sandboxed rendering.







