DeepSeek Harness Plugin

DamonKoy/dsh-web-ui#dsh-pet

Stars ★ 0 Category UI Enhancements Added 2026-08-17

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.
  • 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):

  1. Built-in: assets/<dir>/pet.json in this package.
  2. 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.
  3. Composed: 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.

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
idle waiting running jumping
waving review failed move left/right
waving review failed running-left running-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, and turn/end events into waiting/thinking/tool/review/done/failed states. Optional legacy activity/status events 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: 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.
  • 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 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.
  • 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

BSD-3-Clause

Content from the project README on GitHub ↗

Links

More in this category

View the whole category →