<div align="center">
🐾 DeepSeek Harness Desktop Pet Plugin
A Codex-style desktop pet: embedded in the DSH web UI, playing animations that follow the agent's real-time state, with sprite and pacing fully configurable from the Settings page.
English · 中文
!License: MIT !Node >= 18 
</div>
---
Features
- 🏠 Embedded in the 3080 page: injects an overlay via the official
webServer.tapIndex— the pet lives on the same origin and page as dsh, appearing and disappearing together with it - 🎬 Real event-driven: subscribes to the root context's
session/eventand switches between idle / thinking / working / waiting / completed / failed animations based onturn/start,tool/call,turn/end, etc. - 🖼️ Sprite as config: grid layout and state→row mapping are declared in configuration (
config.spriteor a same-name.jsonsidecar file) and fetched by the renderer at runtime — swapping sprites requires no source changes - ⚙️ Managed in Settings: a visual "Desktop Pet" section in the dsh settings page — upload/select sprites, grids, state mapping, scale, animation speed, idle delay, show/hide
- ⚡ Changes take effect instantly: config is hot-pushed to the renderer via an SSE
configevent; scale, animation speed and idle-delay adjustments apply immediately, no refresh or restart needed - 💾 Automatic persistence: all settings are saved to
pet/user-config.json, restored on refresh, and take priority overcordis.patch.yml - 🪟 Multiple presentation modes: besides the embedded overlay, the renderer also powers a standalone popup page, a Chrome app frameless window, a pywebview transparent window (packagable as .exe), and Electron — four ways to use it
Installation
Prerequisites
- DeepSeek Harness (
dshCLI) installed, with the web profile run at least once - Node.js ≥ 18
Steps
# 1. Register the local plugin to the web profile
# (the bundle takes effect automatically — no manual edits to dsh files):
dsh plugin --profile web add <path-to-this-directory>
# 2. Start dsh (the Web GUI starts along with it):
dsh --profile webOnce started, open dsh's web address (printed in the terminal, usually http://127.0.0.1:3080/) — the pet appears in the bottom-right corner; the management entry lives at Settings → Desktop Pet.
Uninstall
dsh plugin --profile web remove dsh-pet
# User data (pet/user-config.json and pet/uploads/) is kept;
# delete them manually if you want a full cleanup.> Client-side changes only (sprite, scale, animation speed, etc.) just need a forced browser refresh; host-side changes (routes/logic in index.js) require restarting dsh.
UI Reference
The management panel lives in the "Desktop Pet" section of the dsh settings page (the settings.section slot). What each area does:
| Area | Description |
|---|---|
| Enable | Toggle for showing the pet, effective immediately (the overlay hides instantly when off) |
| Sprite | Previews the current image; "Upload Image" picks any local sprite sheet (webp / png / gif / jpg / svg), stored automatically into pet/uploads/; uploaded images can be deleted; "Default (Doraemon)" always switches back to the built-in image |
| Grid | Columns / rows. The sprite sheet is evenly divided into columns × rows; each row is one state animation |
| State → row mapping | Assign a sprite row (0-based) to each of idle / thinking / working / waiting / failed / completed |
| Look & pacing | Scale (0.5×–3×), animation speed (0.25×–4× global multiplier), idle delay (ms) |
| Actions | Save (writes pet/user-config.json and pushes via SSE), Reset to defaults |
Data Storage
- Config:
pet/user-config.json(enabled,scale,fpsScale,idleMs,spriteFile,spritegrid/state mapping, etc.) - Uploaded images: the
pet/uploads/directory (config stores only the file name — large images are never embedded into the JSON) - Both are runtime user data, ignored by
.gitignoreand never committed; to share defaults across a team, use thecordis.patch.ymlconfig instead
How It Works
- Injection: the plugin obtains dsh's built-in
webServerservice and usestapIndexto write<script type="module" src="/dsh-pet/overlay.js">into the 3080 page; sprite, state and SSE all live under same-origin/dsh-pet/*routes - State machine: subscribes to the root context's
session/event, mapsevent.typeto a pet state and plays the corresponding animation row (see table below) - Layout delivery:
/dsh-pet/sprite-configreturns the grid and state mapping; cell pixel size is computed by the renderer as actual image size ÷ cols ÷ rows, so sprite sheets of any resolution are sliced correctly - Hot update: changes from the settings panel are persisted via
POST /configand broadcast to the SSE stream; the renderer applies theconfigevent immediately (the image is reloaded only when the sprite file actually changed, avoiding animation flicker)
State Mapping
dsh event (event.type) | Pet state | Animation row | Notes |
|---|---|---|---|
turn/start | thinking | Row 8 | A turn begins, the model is thinking |
step/start | working | Row 1 | Entering an execution step |
assistant/message, assistant/chunk | thinking | Row 8 | Model is generating |
tool/call | working | Row 1 | Calling a tool (bubble shows the tool name) |
tool/result | working | Row 1 | Processing a tool result |
turn/end (reason.kind is completed) | completed | Row 4 | Task completed; returns to idle after idleMs |
turn/end (reason.kind is error/aborted/interrupted/blocked) | failed | Row 5 | Execution interrupted |
> dsh's agent state is recorded through the session event log (session.append(...)) and exposed to host plugins uniformly as the root context's session/event; agent/error and agent/status are dispatched on the agent's private dispatch, not in session/event, so this plugin uses turn/end's reason.kind as the failure signal. The waiting state is currently triggered only manually in demo mode.
Configuration
Override the defaults in your profile's cordis.patch.yml (cordis.patch.yml must be a top-level YAML array, each element being a patch entry):
- insert:
- id: dsh-pet
name: dsh-pet
config:
idleMs: 3000
scale: 1
fpsScale: 1
injectOverlay: true
overlayRoute: /dsh-pet| Option | Type | Default | Description |
|---|---|---|---|
idleMs | number | 3000 | How long after completion/error before returning to idle (ms, 500–600000) |
scale | number | 1 | Overlay size multiplier (0.5–3) |
fpsScale | number | 1 | Animation playback speed multiplier (0.25–4; a global multiplier applied to all states, without overriding per-row rowFps relative pacing) |
injectOverlay | boolean | true | Whether to inject the overlay into the 3080 page; set false to only serve the /dsh-pet/ popup page |
overlayRoute | string | /dsh-pet | Same-origin route prefix under which all resources are served |
enabled | boolean | true | Whether to show the pet; also toggleable in the settings panel (persisted to pet/user-config.json) |
spriteFile | string | doraemonsprite.webp | Sprite sheet file name (relative to the pet/ directory) |
sprite | object | see below | Sprite grid and state-mapping config (partial overrides supported, deep-merged with defaults) |
sprite config structure:
sprite:
cols: 8 # grid columns (max frames per row)
rows: 9 # grid rows (number of animation states)
fps: 8 # default frame rate (overridable per row via rowFps)
rowFrames: { 0: 6, 1: 8 } # optional: per-row frame count (defaults to cols)
rowFps: { 8: 12 } # optional: per-row frame rate (defaults to fps)
stateRows: # dsh state -> grid row number
idle: 0
thinking: 8
working: 1
waiting: 3
failed: 5
completed: 4Animation layout of the default sprite (doraemonsprite.webp, 9 rows × 8 columns):
| Row | dsh state | Default frames | Default fps | Meaning |
|---|---|---|---|---|
| 0 | idle | 6 | 6 | Idle |
| 1 | working | 8 | 12 | Running right |
| 2 | (unmapped) | 8 | 12 | Running left |
| 3 | waiting | 4 | 5 | Waving |
| 4 | completed | 5 | 8 | Happy |
| 5 | failed | 9 | 10 | Faint / failure |
| 6 | (unmapped) | 6 | 10 | Jumping |
| 7 | (unmapped) | 7 | 9 | Laughing |
| 8 | thinking | 6 | 8 | Thinking |
Standalone Window Mode
Besides the embedded overlay, the pet renderer also provides a full-screen transparent page, suitable for running the pet as a standalone "desktop pet" window:
http://127.0.0.1:3080/dsh-pet/# Chrome app mode (frameless window)
chrome --app="http://127.0.0.1:3080/dsh-pet/" --window-size=256x320
# pywebview + pyinstaller packaged into a single-file .exe (recommended for offline use)
cd dsh-pet/pet
pip install pywebview
python launch.py # local preview
pyinstaller --onefile --windowed --add-data "doraemonsprite.webp;." --add-data "pet.html;." launch.pyWhen dsh is not connected, the standalone page automatically cycles through all state animations (demo mode); press D to switch states manually.
File Structure
dsh-pet/
├── index.js # Cordis plugin entry (hooks into webServer: overlay injection + same-origin routes + SSE)
├── cordis.patch.yml # dsh bundle declaration (top-level YAML array)
├── package.json # npm package and dsh client/bundle config (exports must include ./package.json)
├── LICENSE # MIT license
├── README.md # Chinese README
├── README.en.md # This file (English)
├── lib/
│ └── client.js # Client bundle: mounts the management panel into dsh's settings.section slot
└── pet/
├── doraemonsprite.webp # Default sprite source (9 rows × 8 columns)
├── pet.html # Full-page pet wrapper (thin wrapper, standalone window mode)
├── pet-render.js # Shared rendering logic (reused by overlay and full page, incl. SSE config hot update)
├── overlay.js # Overlay entry (injected into the 3080 page, sprite only)
├── settings-panel.js # Management panel (vanilla JS, mounted in the settings page)
├── launch.py # pywebview transparent window launcher (optional)
├── uploads/ # User-uploaded sprites (runtime-generated, gitignored)
└── user-config.json # Persisted config of the management panel (runtime-generated, gitignored)Development
node --check index.js pet/pet-render.js pet/settings-panel.js pet/overlay.js # syntax check
dsh --profile web # takes effect after restartHost-side changes (index.js) require a dsh restart; the settings-page client entry depends on package.json's dsh.client.inject (real module IDs) and exports["./package.json"] — missing either one causes the client entry to be silently dropped from the boot manifest, leaving no "Desktop Pet" section in the settings page.
Security Notes
- All data is stored locally only (the
pet/directory and the dsh profile); nothing is ever uploaded - The sprite upload endpoint accepts image formats only, with a filename whitelist check to prevent path traversal
- No keys or credentials are collected; all resources are served on dsh's own service, accessible only locally
🤖 AI Disclosure
This project was developed with DeepSeek Harness.
License
[MIT](LICENSE) © Levi5