PluginWorld
Ds

dsh-pet-plugin

dsh✓ SPEC VERIFIED

DSH 桌面伴侣插件:五只超写实立绘(表情 + 动作序列)、主动说话与神经音色、小输入框单聊(一次性模型调用,不进会话)。

@zuoyunlai · v1.27.1 · MIT · updated 4d ago

SECURITY

A

SCORE

76

INSTALLS

▲ 5.8K

PLUG IN

npx @deepseek-ai/dsh web

Launch, then search "dsh-pet-plugin" in the built-in market to plug it in

README

dsh-pet

A little companion that lives in the bottom-right corner of the DSH web UI: it stays around or drops in at random, talks (with voice), walks, dances, gets shy or annoyed, and carries a tiny input box you can chat with. Five photo-realistic personas, each with its own temper.

Scope: the pet only lives inside the DSH window (it mounts into the framework's overlay slot). It cannot float above other applications — that would need a separate Electron shell rather than a plugin.

At a glance

  • Languages: UI chrome, persona taglines and the chat system prompt follow the browser interface language — shipped for en / zh / es / pt / hi (locale/*.json, namespace pet; slots register with locale: 'pet' per the DSH slot contract). Each persona also ships an English line pack, so a non-Chinese UI hears English lines.

  • Mount points: shell.overlay (the pet itself, a click-through layer) and conversation.input.right (the entry button next to the composer).

  • Private chat: what you type in the small box never enters your session. The host half exposes POST /dsh-pet/chat and answers with a one-shot ctx.llm.stream() call (no session, no session log). Defaults: deepseek-account / deepseek-flash; provider, model, maxTokens and temperature are plugin config.

  • Icon: icon.svg (the rose silhouette) and the composer button share the same path.

  • Settings panel: grouped (look / voice / display / actions / effects), custom dropdowns (native <select> popups are system-white and unreadable in dark themes) and custom switches; it is placed relative to the pet (above her, or to her left when there is no room) and clamped into the viewport under the header.

  • Five personas (photo-realistic, same lighting; all five ship 5 expressions each: default / happy / shy / angry / sleepy): 薇 (violet), 绯 (crimson), 靛 (indigo), 雪 (silver), plus 鲸鱼娘 (azure) — a whale-fin maid whose expression set was added in 1.23.0, with its own walk / dance / hair clips. Expression changes cross-fade (180 ms) instead of hard-cutting.

  • Action sequences: a persona may declare actions: { dance: [...], hair: [...], walk: [...] }; each set is 3–6 frames (3-frame sets step at 340 ms, 6-frame at 230 ms; azure ships clips only, no frame sets). All four originals have dance / hair-flip / walk, each stylised to the character; newer personas may instead ship video clips (VP9 + alpha), and a clip's own length decides how long the action lives.

  • Adding artwork: drop transparent PNGs into assets/ and reference them from PERSONAS (image, or images: { mood: 'file.png' }). Served by /dsh-pet/assets/ (image and webm video extensions only, basename only). tools/make-pet-image.py removes a green-screen background (preferred) and falls back to a gradient key.

Behavior

Capability How
Always-on / random Toggle in the panel; random mode rolls a per-hour probability inside the active window
Active hours Start/end time, supports crossing midnight (22:00 → 03:00)
Proactive lines Per-mood line packs, bubble + speech
Voice Optional: if the host has any python with edge-tts, /dsh-pet/tts synthesises (cached) and /dsh-pet/tts/status probes it; otherwise it falls back to the system speechSynthesis. The plugin itself has zero runtime dependencies
Small input box Hidden by default; slides out when you hover the character (since 1.22.0, and moving from the character onto the box does not dismiss it). Turn off "Only on hover" in settings to pin it. Messages go to the pet only; one-shot model call, nothing is written to any DSH session
Moods idle / walk / dance / shy / angry / sleep / talk / happy
Dragging 60% chance of a drag line once you drop it; all five personas have them (completed in 1.24.0), five languages
Inner voice A low-frequency (7%) grey aside — a thought, never spoken; only for personas that declare lines.aside; all five have them (1.24.0)
Gaze Pupils follow the mouse (CSS variables only, no re-render)
Effects Ink / confetti / cloud / fog (wipable) / bomb + cracked screen
Event hooks Confetti after a turn longer than 4 s; a cloud appears after a failed tool call
Temper Per-persona weight table decides what it does more of
Hide "Hide for 1 h" in the panel

Install (local, no registry)

Published as dsh-pet-plugin on npm; it also installs straight from a folder.

# from the registry (after publish)
dsh plugin --profile desktop add dsh-pet-plugin
# from a local checkout: quit DSH first, then
powershell -NoProfile -ExecutionPolicy Bypass -File .\install.ps1    # or double-click install.cmd

Quit DSH first (the profile's package.json is HMR-watched). The script adds "dsh-pet-plugin": "file:<this dir>" to the profile, registers the bundle, runs pnpm install, then reads back all four facts. update.cmd only refreshes files and is safe while DSH is running. For the sweeter neural voice:

pip install edge-tts                     # any python the host can find
export DSH_PET_PYTHON=/path/to/python    # only if auto-discovery misses it

Verify

node --check client.js
node tests/smoke.mjs                     # fake window + React stub, renders every persona/mood
# host routes need the harness packages, so run them from the installed copy:
cd <profile>/node_modules/dsh-pet-plugin && node --test tests/host.test.mjs
node <dsh-plugin-guide>/bin/dsh-plugin-dev.js check --cwd . --strict
node <dsh-plugin-guide>/bin/dsh-plugin-dev.js verify --cwd .

Customize

  • The client bundle is generated: edit src/*.js and run node build.mjs, which concatenates the parts into client.js (the only file DSH loads). Never edit client.js by hand — npm test rebuilds and then runs the smoke test.
  • Personas, lines and tempers: pets/<id>/pack.json — images (five expressions), actions (dance / hair / walk, 3–6 frames each), voice, temper, and lines / linesEn line packs.
  • UI strings: edit the pet namespace in locale/*.json, then node build.mjs — the dictionaries are injected at build time, so the locale files are the single source of truth. New language: run node tools/gen-locales.cjs for a skeleton, then translate.
  • Moods and animations: add keyframes in src/70-style.js and map the class by mood in src/96-overlay.js.
  • Action clips: a pack may carry clips: { dance: "pet-x-dance.webm" } — a VP9 WebM with an alpha channel. Clip heights are tiered (1.25.0): stage clips 960 (single-generation re-export from the 2K masters, for the zoomed dance on 4K displays), hair/walk of the four originals 768 (native from the 768 masters), the rest 640; ordinary clips are about 5.167s and stage clips about 10.125s. tools/assets-manifest.cjs records dimensions, duration, codec, pixel format, alpha mode, byte size, and SHA-256; the asset test enforces those values and a 20 MB aggregate budget. When that action plays the pet renders a looping <video> instead of flipping through still frames, which is the only way to get true 24 fps motion.
  • Artwork: drop transparent WebPs into assets/ and reference them from pets/<id>/pack.json, then node build.mjs (it regenerates src/30-personas.js). The /dsh-pet/assets/ route only serves allow-listed image and webm video extensions.

Extending at runtime (1.21.0)

  • Custom persona packs without a rebuild: drop $DSH_HOME/dsh-pet/pets/<id>/pack.json (with its assets next to it; license is a release gate for the built-in packs — user packs are not license-checked at runtime) — the host scans it at startup, merges it with the built-in five, and serves it under /dsh-pet/pets/<id>/… (realpath containment + extension whitelist + 413 oversize). A pack must satisfy the render contract, or it is refused whole at load (fail-closed, one warning in the host log): name + clips, image (or, for SVG personas, a complete colors table), and — when voice / temper are present — voice.pitch / voice.rate as finite numbers and temper as mood → positive number. The client picks the list up via GET /dsh-pet/personas and falls back to the built-in table when absent.
  • Line overrides: $DSH_HOME/dsh-pet/voice.json — {"<personaId>|*": {"lines|linesEn|linesEs|linesPt|linesHi": {"<mood>": ["…≤300 chars, ≤200 lines"]}}}. Merged per mood, warn-and-drop on any malformed piece; a broken file is ignored with a warning, never a crash.
  • Make the pet speak: POST /dsh-pet/announce {"text": "…"} (≤300 chars, 30/min rate limit). Poll /dsh-pet/ping for phase (working|tool|review|done, 15s freshness) and announce (seq-guarded) — phase drives walk/hair micro-actions while the agent works.
  • State on the host: GET/POST /dsh-pet/state. Both sides carry updatedAt (newest wins, so an old host snapshot can never roll back a fresh local change); the client debounces a POST 500 ms after every settings/history change. History is stored as {role, content} objects — legacy "[object Object]" strings are dropped and migrated instead of being written back. Settings keys are whitelisted host-side (≤64 entries, history ≤200 items × 8000 chars).
  • Silence auto chatter: settings statusBubbles: "auto" | "off" mutes mood/greeting/idle bubbles; taps, drags and announce still speak.
  • Licensing: every pack declares license; third-party notices ship in THIRD_PARTY_NOTICES.md; the asset test enforces both.

Security model

  • The host routes are gated three ways, none of them tied to a fixed port or domain: Host must be a loopback address, the host's own origin, or listed in the allowedHosts plugin config (for reverse-proxy / LAN deployments); a present Origin must be same-origin with that Host or the desktop shell (dsh-app://app, dsh-app://shell); and Sec-Fetch-Site, when present, must be same-origin or none. Non-browser clients that send none of these are allowed — that is the deliberate limit of a local HTTP endpoint, see below.
  • Speech synthesis is a POST endpoint that returns the audio bytes; there is no GET URL to feed to an <audio src>, so a page (or another local process) cannot make the host synthesise anything by pointing an element at it.
  • Synthesised speech is cached in the OS temp dir with a size cap that is re-checked after every synthesis, and writes go through a random temp name + rename so a pre-planted symlink cannot redirect them.
  • Anything the page sends (settings, chat history, persona id) is treated as data: validated, length-capped, and never interpolated as instructions or markup.
  • Neural voice sends the spoken text to Microsoft's edge-tts endpoint. Turn it off (or never install edge-tts) to keep everything local; the panel says so.

Known limits

  • Host version: built and tested against dsh 0.2.1-alpha.2. It needs the webServer service and the shell.overlay / conversation.input.right slots, which are 0.2-generation APIs — on a 0.1.x host it installs but never mounts (one console line says so). engines.dsh in package.json is a declaration only: the installer does not enforce it, and the plugin declares no @deepseek-ai/dsh-* peer ranges on purpose, so a version mismatch never blocks installation.

  • Neural voice needs a host python with edge_tts; otherwise the system SAPI voice is used.

  • Browsers block autoplay before the first user gesture — click the page once.

  • Settings live in localStorage. The debug handle (globalThis.__dshPet) stays off unless you set dsh-pet.debug=1 in localStorage or append ?petdebug=1.

  • The pet cannot float above other applications (overlay slot only).

SIMILAR PLUGINS