A Taffy mood mascot for the DeepSeek Harness Web UI: the corner GIF reacts live to agent state — approval pending / approved / rejected / begging, thinking, tool runs, interrupts, ask_user_question waiting, compaction, long-task fatigue, idle-ignored approval, idle / sleeping — with SSE-pushed state, drag positioning, middle-click console, tool-card mirror console, per-mood locking, persisted appearance settings and per-day bucket statistics.
Install
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:vegetable-kun/DSH_Plugin_Taffy
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 — pnpm blocks those until you allow them, so an install can stop with ERR_PNPM_GIT_DEP_PREPARE_NOT_ALLOWED or ERR_PNPM_IGNORED_BUILDS; dsh prints the exact key to add under allowBuilds in your profile’s pnpm-workspace.yaml, and the install works on the next run. Allowing a build is a trust decision: only install sources you trust, and pin a commit (github:owner/repo#sha).
README
中文简体 | English
A standard DSH plugin that pins a Taffy mood mascot in the bottom-right corner of the DeepSeek Harness Web UI. The GIF reacts live to agent state — approval pending, thinking, tool runs, interrupts, errors and more — each with its own expression. One-line install, assets bundled, no build step.

Install
dsh plugin --profile web add github:vegetable-kun/DSH_Plugin_Taffy
Then restart dsh web. The installer auto-registers the package in your profile's bundle list — no manual config needed.
Mood description
| Mood | Asset | Trigger |
|---|---|---|
| Idle | taffy2-idling |
Default fallback |
| Typing | taffy-se_xy |
Composer has unsent draft text |
| Thinking | taffy-dumb |
Model is streaming |
| Tool running | taffy-tang-laughing |
tool/call is active |
| Awaiting approval | taffy-fork |
An approval request pops up (pulsing animation) |
| Approved! | taffy-spread_heart |
3 s after approval |
| Hacker | taffy-hacker |
While executing an approved task; clears when the turn ends |
| Embarrassing | taffy-embarrassing |
Persists after rejection until you start typing |
| User aborted | taffy-angry |
turn/end reason=aborted, 5 s |
| Error / max-tokens | taffy-suicide |
turn/end reason=error or max-tokens, 5 s |
| Interjection! | taffy-suprised |
User messages while model is still streaming / working, 4 s |
| Blocked | taffy-cry |
turn/end reason=blocked, 6 s |
| Proud | taffy-admirable |
A turn finishes after ≥5 tool calls, 4 s |
| Fake crying | taffy-fake_crying |
Two consecutive rejected approvals, 5 s |
| Begging | taffy-begging |
Second approval request within 60 s, 5 s window then falls back to fork |
| Awaiting answer | taffy-staring (static) |
An ask_user_question call is pending; clears on user reply |
| Compacting memory | taffy-pressure (static) |
compaction/start → end (with 90 s safety net) |
| Long-task fatigue | taffy-angry_staring (static) |
A turn runs longer than 3 min, clears on turn/end |
| Ignored approval | taffy-cry_denying |
An approval sits idle for more than 30 s, clears on decision |
| Sleeping | taffy4 (static) |
Idle for more than 10 min, saves GIF decoding; any activity wakes it |
| Greeting | tafei (static) |
2 s hello when the page loads; never blocks real moods |
Priority chain: lock > begging (time-boxed) > awaiting answer > compacting > ignored approval > approval pending > approval celebration > surprised > crying > aborted / error / fake-crying flash > proud > fatigue > running (hacker / thinking / tool) > typing > greeting (2 s) > sleeping > idle. Every state has a clear exit: begging is a time-box, awaiting answer is cleared by reply / matched result / turn end, compacting is bounded by end plus a 90 s safety net, fatigue and ignored-approval are time-derived and disappear on the next event, so no state can permanently block lower ones.
Usage
- Left-drag to reposition; middle-click opens the floating console; right-click is a fallback.
- The console shows a live status badge, lets you lock any mood (for debugging), run a test approval, and reset the position.
- Settings → Taffy 表情: enable toggle, size (50–1000 px), opacity, click-through, and a today's mood-time summary.
- Appearance settings persist in the browser's
localStorageunderdsh-taffy-mood/settings; they survive page refresh.
Uninstall / disable
dsh plugin --profile web remove dsh-taffy-mood # remove from profile
# or just untick "启用表情" in settings to hide it
Configuration
Per-mood time thresholds are read from ~/.dsh/profiles/web/cordis.patch.yml — add a config block to the dsh-taffy-mood row (snake_case, only override what you want):
- id: dsh-taffy-mood
config:
surprised_ms: 6000 # interjection window 6 s (default 4000)
ignored_after_ms: 60000 # ignored-approval 60 s (default 30000)
sleep_after_ms: 600000 # sleep after 10 min (default 600000)
tired_after_ms: 300000 # fatigue after 5 min (default 180000)
# turn_flash_ms / crying_ms / admirable_ms / begging_window_ms / begging_show_ms / compacting_failsafe_ms — same idea
Omitted fields keep the default; invalid values (negative, non-number) are ignored and fall back to the default.
Structure & technical notes
package.json dsh.bundle.patch + dsh.client.platform: "web"
cordis.patch.yml - insert: dsh-taffy-mood
host.js Host half: session/event state machine + bundled assets + JSON API
client.js Client half: __ModuleLoader__-wrapped overlay / console / settings panel
assets/ All assets (15 in-use GIFs + spare JPGs)
test/smoke.mjs Smoke test: `node test/smoke.mjs`
test/client-act.mjs Client-side unit test for action URL shape
- The host subscribes to
ctx.on('session/event')and drives the state machine off log audit events (approval/*,tool/*,turn/*,assistant/chunk,user/message,compaction/*); running state is read live from the agents registry (event counts miss agents that were already running at plugin activation). - The browser side polls
/dsh-taffy-mood/api/stateand posts actions to/api/action(lock / clear-rejected / test-approve); assets are served from/dsh-taffy-mood/assets/*with a content-hash whitelist route and an in-memory cache. - Write actions are strictly POST-only with an Origin check (only
127.0.0.1/localhostloopback, or a missing Origin for local tools) — prevents cross-origin pages from triggering approval cards via<img>tags. - Polling cadence is demand-driven: 300 ms when a mood is active, 2 s when idle, and time-windowed when a time-bounded state is about to expire.
- Interjection detection: tool-call in progress or a chunk within 2.5 s — avoids false positives on the very first poll of a new turn.
- Typing detection uses the official
conversation.composer.dockInputZone'sprops.input.draft(no key listeners). - Per-mood time thresholds can be overridden via the
config:block in~/.dsh/profiles/web/cordis.patch.yml(snake_case), e.g.surprised_ms: 6000/sleep_after_ms: 600000. - Assets are content-hashed for permanent caching (
max-age=31536000, immutable); URLs look like/assets/taffy-cry.a1b2c3d4.gif— new package version ⇒ new URL ⇒ no stale cache, and old URLs redirect with 301 for compat. - Pure ESM host +
__ModuleLoader__-wrapped client, no build step, no npm dependencies — clone the repo and you have the running source.
欢迎提 issue 和 PR — if you like it, a free ⭐ would be lovely.
License
MIT
Links
More in this category
PC2005-cloud/dsh-pet#dsh-pet★ 1228
Desktop pet for the DSH Web UI with 25 transparent animations, screen wandering, click reactions and drag, plus a reproducible asset-generation pipeline.
Nagi-ovo/dsh-ads★ 656
Parody ads in 2005-Chinese-web style: sidebar banners, in-chat feeds, corner popups, and a close button whose hit area is smaller than it looks. All fictional.
vlln/whale-girl★ 356
Desktop pet (QQ-pet style): floats in the corner, draggable, feedable, playable.
yyh-001/dsh-meme★ 169
Chat meme stickers: text-only send, mood auto-send, QQ/WeChat-style picker, auto-learn, custom packs.
a86582751/dsh-nexttavern★ 163
Roleplay workspace for DeepSeek Harness: import SillyTavern/TauriTavern character cards or author one from scratch through an interactive flow; turn a long TXT novel into a playable card with either an intensive or a coarse reading pass; the main agent consults worldbooks on demand, and prose is read in a dedicated tavern reader tab; explore multiple worldlines inside one conversation (regenerate, edit-and-send, explicit clone); scoped style presets with 16 built-in styles, a character agent cluster, a standalone decision card, and card or novel export; long stories hold together through a frozen settings prefix, a hard-cut context window, traceable director notes and keyword/semantic/hybrid recall, with embeddings either online or fully local. Targets Harness 0.1.2-alpha.3 and applies its documented compatibility patches.
Sutera-Diffusus/dsh-whale-musume★ 119
A whale-girl desktop pet for the DeepSeek Harness Web UI: pat-to-raise growth, work-state poses per tool type, 494 dialogue lines, 30 achievements, a growth diary, drag physics, theme sync and an accessibility mode. Chat-keyword sensing and API-balance care ship off and are opt-in; an optional weather mood calls Open-Meteo only after you name a city. No telemetry.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.