dsh-pet
PC2005-cloud/dsh-petDSH Web UI 桌面宠物:25 个透明动画、屏幕漫游、点击反应与拖拽,附可复现的素材生成链。
DSH 网页 GUI 实时桌宠:可把任何 Codex 桌宠图集(webp/png/gif,v1/v2 自动识别)转换为 DSH 中可拖拽的悬浮桌宠。支持拖拽/挥手/跳跃,v2 桌宠的眼睛实时追踪活动鼠标,随 agent 状态切换姿势;进度气泡反映工作状态;「设置 → 桌宠」图形界面可导入、切换与调整桌宠;内置一只原创示例桌宠(娜斯佳),用户桌宠存放在 ~/.dsh/pets。
A DSH web-GUI pet runtime plugin — one plugin that renders *any* Codex spritesheet-atlas pet (v1 / v2) as a draggable desktop pet in the DeepSeek Harness web GUI.
It is the runtime half of the codex-to-dsh-pet framework: instead of building each pet into its own plugin bundle, you install this plugin once, then add pets as plain data files — no per-pet code.
The plugin ships one built-in example pet `nastya` (娜斯佳) — an original
character (CC BY-NC 4.0, non-commercial) — so the registry is never empty.
See assets/nastya/.jump-on-double-click.
has an Eye tracking toggle; the UI notes it applies to v2 atlases only (v1 has no look cells, so the toggle is inert for v1 pets).
conversation state.
shown when hovering the pet. Automatic summaries observe only requests completed after selecting a session; they do not backfill historical conversations. Right- click the pet to explicitly analyze prior unsummarized requests. This is disabled by default. Enabling it sends bounded excerpts of user, assistant, tool, and error content to the selected LLM and incurs that provider's usage/cost. Select a provider and model before enabling summaries. Cadence and bubble duration are configurable.
volume, bundled rotating takes and user-uploaded audio overrides.
/api/codex-pet/*.DSH gates every profile plugin row on its @deepseek-ai/dsh* peerDependencies: a peer range the running DSH version does not satisfy disables the whole row (no pet, no settings section) with Plugin … is incompatible with dsh …: peerDependencies ….
This plugin therefore declares only peers that exist across the DSH 0.2 line and later, with open-ended ranges:
"@deepseek-ai/dsh-client-ui-slots": ">=0.1.0-rc.6",
"@deepseek-ai/dsh-host-webserver": ">=0.1.0-rc.6"It works on 0.1.x and on 0.2.0-rc.1+. The obsolete @deepseek-ai/dsh-client-runtime peer is *not* declared: DSH 0.2.0-rc.1 removed that package, so declaring it (^0.1.0-rc.6, as 0.3.1 did) makes DSH disable the row — which is exactly why the pet vanished on 0.2.0-rc.1.
The same upgrade also retired the client runtime at runtime, not just in the manifest. On 0.1.x the pet overlay read the conversation from sessions.currentProvideInfo; that bridge is gone in the 0.2 line, which would leave the snapshot null and the bubble permanently blank (0.3.2 had this bug — the pet appeared but never spoke). The overlay now reads the internal uiSession service's main binding instead:
hooks.chat → the live tail (partial, runningCalls) plus turnEnds;this is the target dsh-client-ui-chat registers, i.e. the streaming reply text and the "running: \" bubble text;
hooks.trajectory → the same fields plus requests, kept as a fallback whenthe chat hook is absent (its own partial only fills in once its target has accumulated chunks);
hooks.session → running, pendingSubmissions, openState;nodes / turnEnds are rebuilt from the trajectory event nodes, keeping thedone chime and the periodic summaries working. ui-trajectory pushes the finalized node itself into eventNodes ({ kind: "user" | "assistant" | "tool-result", seq, turn, step, blocks, … }), so that is the shape read here; the older { data: { kind, node } } contribution wrapper is still accepted as a fallback. Reading only the wrapper is what made every summary claim the pet had done nothing (0.4.1 and earlier): the node array came out empty, so the summarizer received no instruction, no reply text and no tool calls.
uiSession is deliberately not listed in inject: it is an internal service that older hosts never provide, and Cordis parks the entire plugin when a hard dependency is missing. It is resolved lazily with ctx.get("uiSession") (plus a short polling subscribe, because the service can arrive after the plugin loads), and when it never shows up the overlay falls back to the legacy currentProvideInfo path — so one build serves both hosts.
Recommended — one command (needs pnpm):
# from npm
dsh plugin --profile web add @signalight/dsh-codex-pet
# or straight from GitHub
dsh plugin --profile web add github:Signalight/codex-to-dsh-pet#path:/packages/dsh-codex-petManual route (no pnpm): from the repository root run
.\install-runtime.ps1This copies the package into ~/.dsh/profiles/node_modules/@signalight/dsh-codex-pet and registers the plugin row in ~/.dsh/profiles/web/cordis.patch.yml. Then:
http://127.0.0.1:3080 (Ctrl+Shift+R) — the DSH profile hot-reloadscordis.patch.yml. If the pet still doesn't show, fully quit and relaunch the DSH desktop app (command-line users can restart dsh web).
Rollback: delete ~/.dsh/profiles/node_modules/@signalight/dsh-codex-pet and restore the .bak next to the patch file.
A pet is a folder holding a pet.json manifest plus one atlas image. Drop it into any source (later sources override earlier ones on id collision):
packages/dsh-codex-pet/assets//~/.codex/pets// — read in place, nothing is copied;point CODEX_HOME elsewhere to use a different Codex home
~/.dsh/pets// (survives plugin updates)Resolution order is built-in → Codex → user, so a pet you import overrides the same id coming from Codex, which overrides the bundled example. The settings surface groups the gallery by these three sources and puts a v1 / v2 badge on every card.
pet.json:
{
"id": "nastya",
"displayName": "娜斯佳",
"description": "娜斯佳 Nastya — original character (CC BY-NC 4.0).",
"spritesheetPath": "spritesheet.webp",
"spriteVersionNumber": 2,
"size": 120,
"pin": "bottom-right"
}Only id and spritesheetPath are required. spriteVersionNumber is auto-detected from the image dimensions when omitted (1536×1872 = v1, nine rows; 1536×2288 = v2, eleven rows); size (px width) and pin (top-left / bottom-right / …) default to 120 and bottom-right.
GET /api/codex-pet/state reports codexPets: { enabled } and codexPetsDir, and POST /api/codex-pet/set-config accepts { "codexPets": { "enabled": false } } to stop reading them (the default is true). Toggling the source rebuilds the registry immediately and re-seats a selection that no longer exists, so the pet never points at a vanished atlas. resetAll() puts the switch back to its default.
The browser half renders the picker as a gallery: each card shows the atlas's first cell as a thumbnail, mounted only while the card is near the viewport (IntersectionObserver, rootMargin: 300px) and unmounted once it scrolls away — a decoded atlas is roughly 14 MB, so keeping a dozen of them alive would stall the settings page.
No manual editing needed: in the DSH settings surface open the «桌宠» section and click 导入桌宠 (webp / png / gif) — the plugin writes the file into ~/.dsh/pets//, auto-detects the atlas version, and selects the new pet. The pet id comes from the filename (my-pet.webp → my-pet); if that id is already taken the plugin appends a -2 / -3 suffix instead of overwriting the earlier pet (all Codex atlases are named spritesheet.webp, so without this every import would clobber the previous one). Re-importing the same id with the same typed display name updates that pet in place.
src/
├── index.js host half: name/inject/apply, mounts service + routes
├── registry.js scans assets/* + ~/.dsh/pets/*, normalizes Codex atlases
├── service.js persisted selection + display config (~/.dsh/codex-pet.json)
├── routes.js /api/codex-pet/* JSON API + /codex-pet/<id>/* assets
└── client.js browser half: registry-driven overlay renderer(ctx.webServer.register), serving the pet list, state and atlas assets.
/api/codex-pet/state, renders the selected pet,mirrors drags back via POST /api/codex-pet/set-config, and seats a settings.section entry («桌宠») that edits the pet through the same API. It also carries the two recovery buttons (position / everything) under «还原 Reset», and re-seats the pet in place whenever the host bumps display.layoutRev — a dragged left/top outranks the pin, so clearing it changes none of the fields the pet is recreated for. Bubbles, the journal and the context menu are absolutely positioned with no default coordinates, so the rule is place first, show second: a reveal reads the pet's live rect, falls back to the placement the controller recorded (controller.box, also used to seat the pet itself when a pin is empty or unknown), and otherwise keeps the element hidden while retrying every 100 ms for up to 1.2 s. An unplaced overlay would render at the host origin, i.e. the top-left corner of the screen.
Endpoint · Method · Purpose /api/codex-pet/pets · GET · pet registry (list of definitions) /api/codex-pet/state · GET · selected pet, display config, codexPets switch + codexPetsDir, pet list with source / spriteVersionNumber /api/codex-pet/set-pet · POST · { petId } — switch pet /api/codex-pet/set-config · POST · update display, summary, sound, or { codexPets: { enabled } } /api/codex-pet/set-visible · POST · { visible } /api/codex-pet/reset-position · POST · drop a dragged left/top so the pet is re-seated by its pin /api/codex-pet/reset-all · POST · restore every setting to its default; pet choice and imported files stay /api/codex-pet/import?id=&name= · POST · raw image body — import a Codex atlas /api/codex-pet/models · GET · available LLM providers and models for summaries /api/codex-pet/summarize · POST · summarize one completed assistant-request batch /api/codex-pet/journal?session=&limit= · GET · recent persisted summaries (&all=1 returns all records for manual coverage checks) /api/codex-pet/sound?track= · POST · upload a raw audio override (8 MiB maximum) /api/codex-pet/reset-sound?track= · POST · remove an audio override /codex-pet-sound?track= · GET · stream the effective done, error, or interrupt track /codex-pet// · GET · pet.json + spritesheet assets
Summary journals are stored under ~/.dsh/codex-pet-journal/. Uploaded voice overrides are stored under ~/.dsh/pets/sounds/ and survive plugin updates.
show/hide, size, corner pin, bubble color/opacity, eye tracking — the last is v2-only and noted as such), backed by the plugin's own API.
select).
~/.codex/pets) automatically, with athumbnail gallery, v1 / v2 badges and a default-on switch.
@signalight/dsh-codex-pet) + dsh-plugin GitHub topic.MIT — see the repository root LICENSE.
The plugin ships 8 built-in scenario sounds under assets/sounds/ (done-1~4.wav, error.wav, interrupt-1~3.wav):
(PR #4) using Qwen3-TTS VoiceDesign and submitted with the plugin; they were reviewed and accepted into this package, and are distributed under MIT together with the plugin.
Apache-2.0; that license applies to the model repo only and does not change this project's license. Using Qwen3-TTS does not imply endorsement by Qwen.
built-in example pet nastya (娜斯佳). That character is an original character, and its atlas is licensed CC BY-NC 4.0 (non-commercial), so even though the sound files are MIT-licensed, using them as that character's voice keeps the character's non-commercial restriction in force; ask the character's author before any commercial use.