Sessions & Messages
dsh-pet-extras
amaji61/dsh-pet-extras · ★ 0
DSH 桌宠扩展:让桌宠看你的屏幕、把画面交给一条真实会话来判断,再用桌宠的声音说出来。零第三方依赖。
Manifest details
安装
dsh plugin --profile web add 'github:amaji61/dsh-pet-extras#cae40aa062f65990d42d33baec058c6bfd9869f6'GitHub 源码安装:首次需按提示配置 allowBuilds 构建授权后重试
For install and environment setup, plus plugin development tutorials, see DSH community docs ↗
README
Table of contents
- ⚠️ Read this first: it captures your entire screen
- The agent bridge: this plugin's foundation, not an optional extra
- How this differs from dsh-pet's built-in chat
- Features
- Settings page
- Install
- Desktop app: one-click (recommended, no JSON editing)
- Manual install (pnpm add / offline tgz): two places must be edited
- Verify it actually loaded
- This version no longer touches any dsh-pet file
- The only remaining dsh-pet modification is the optional work-status patch — see Work-status bubble vs. pet chat. If it cannot apply, it skips itself and everything else still works.
- Configuration
- HTTP API
- How it works
- Screenshots
- Agent bridge
- Narration
- Self-learning for narration (off by default)
- Compatibility
- System notifications
- Privacy and data handling (please read before installing)
- What is captured, and where it is stored
- When data leaves your machine
- What else it writes
- Permission notes (for security scanners)
- Known issues (being honest — this release is not perfect)
- Work-status bubble vs. pet chat
- This plugin modifies files in dsh-pet — two patches, here is exactly what and why
- Uninstallation
- FAQ
- Credits
- License
Extra capabilities for dsh-pet, the desktop pet plugin for DeepSeek Harness (DSH).
This plugin does not provide the pet itself. It is meaningless without
dsh-pet— install that one first.This is a hobby project by an individual developer, not an official extension, and not affiliated with the
dsh-petauthor. It is at0.2.3, an early release. This is the author's first time publishing on GitHub, so there may still be bugs. Issues are welcome and I'll do my best to fix them.
中文说明见 README.md.
⚠️ Read this first: it captures your entire screen
If you are not comfortable with this section, please do not install.
| What it does | What you should know |
|---|---|
| Captures a full-screen image of the primary display at an interval you set | Screenshots may contain chat logs, password fields, or other people's private information |
| By default, stores them on your local disk only | $DSH_HOME/dsh-pet/screenshots/. The plugin uploads them nowhere |
| Images are sent to a model only after you enable "watch" yourself | They go to the model provider you configured in DSH, billed to your own API key |
Default state: "watch the screen" is OFF — no screenshots, no network, no model calls. With everything off, it is just a plugin sitting on disk doing nothing.
If you never want images to leave your machine, simply never enable screenshotEnabled.
See Privacy and data handling below for the complete data-flow table.
The agent bridge: this plugin's foundation, not an optional extra
Start with the mechanism — everything else is built on it.
dsh-pet ships with its own small brain: it talks to a default model by itself, reading only its own trimmed memory. That is a completely separate brain from the session you have open in the UI. It does not know what you said there, and you cannot find what it said.
What this plugin does is remove that small brain and point the pet at your real one.
what you say / what is on screen
↓
delivered into a real DSH session ← the very session you normally use
↓
that session answers with its own context and memory
↓
the reply comes back to the pet, spoken in its voice, line by line
What you get out of it:
- One brain. The pet does not keep a second memory. What it says is what your session says. Anything you told that session is known to the pet too.
- It can hold a conversation. No longer a one-shot toy — it carries the last dozens of turns, follows up, stays on topic.
- It can do work for you. What gets delivered is a real message, so the session really calls tools, reads files, runs commands. The pet turns from a talking ornament into a working entry point.
- The screen is its business too. Narration uses the same mechanism: the screenshot goes in as an attachment, the session actually looks at the picture before speaking instead of guessing from a sentence.
The costs, stated plainly:
- Every sentence the pet says and every look it takes spends that session's tokens. Watch more often and you hit the context limit sooner.
- Delivery is serialized: while that session is busy with your task, the pet waits.
- It really enters your session. Do not think of it as "just a pet" — what it does is the same as you sending a message yourself.
This is also why "Watch the screen" must go through this path: sending a sentence like "look at the screen" without the image would leave the session blind, so it could only invent. To narrate, you hand over the image. If you do not want that, do not narrate.
How this differs from dsh-pet's built-in chat
dsh-pet built-in |
with the agent bridge on | |
|---|---|---|
| Who answers | a separately-called default model | the real session open in your UI |
| Memory | its own trimmed memory | the session's full context and memory |
| Knows what you said | no | yes |
| Can call tools and do work | no | yes (these are real messages) |
| Whose tokens it spends | a separate call | that session's budget |
Features
| Feature | Description | Default |
|---|---|---|
| Agent bridge | The foundation. What you say to the pet is delivered into a real DSH session, and that session's reply is spoken back through the pet, sentence by sentence. Shares that session's context and memory. Turn it off and the two below stop working | Off |
| Watch the screen | One switch for the whole thing: grabs the primary display every N seconds (local PNG) and hands that shot to the connected session, which answers in one or two lines using its own context and memory — shown in the bubble above the pet. Turning it on always calls the model and costs tokens; turning it off stops everything. There is no "capture only, never share" mode | Off |
| Sight archive | Every look stores the original screenshot + the session's raw analysis in dsh-pet/视野/ (one folder per day, kept 7 days). Never loaded into context; open it when you want to know what was on screen yesterday afternoon |
On |
| Keep on screen | Pushes the confineToScreen flag to dsh-pet so the pet window cannot leave the screen bounds. Off by default: the plugin only takes over after you set it explicitly via /dsh-pet-extras/confine — installing it never quietly rewrites dsh-pet's config |
Off (unmanaged) |
Settings page
Once installed, DSH settings gains a "Pet extras" section holding the plugin's own knobs: the Watch the screen switch and the Watch interval (sec) field. Save applies immediately.
Why a separate section: these keys live in this plugin's own config file and dsh-pet's settings page knows nothing about them. Previously the only ways in were editing JSON or curl; now there is a UI.
An earlier version of this file claimed the switches were "the same key, synchronised with the
dsh-petsettings page" — no such thing exists. The plugin's config lives only in its ownextras-config.jsonand is edited only from Settings → Pet extras. Nothing is synced anywhere.
Install
Requires dsh-pet first — this plugin is an add-on; it does not provide the pet itself.
Desktop app: one-click (recommended, no JSON editing)
- Settings → Plugins → Add plugin
- Enter the absolute path of the package, either form:
- the
.tgz:D:\some\dir\dsh-pet-extras-0.2.3.tgz - the extracted folder:
D:\some\dir\package
- the
- Confirm, then restart DSH when prompted
You do not need to edit package.json on this path: the plugin manager reads dsh.bundle from the package and writes the name into dsh.profile.bundles automatically (that is reconcile() in dsh-plugin-manager).
Right after installing: go to Settings → Pet extras, pick a session under "Connected session", then switch "Watch the screen" on.
With no session selected the plugin does not capture and does not deliver — deliberately. Earlier builds silently picked "the session with the most messages", and in the wild that flooded an unrelated session with full-screen screenshots. Now: nothing selected, nothing happens.
Manual install (pnpm add / offline tgz): two places must be edited
DSH only loads packages listed in dsh.profile.bundles. pnpm add writes dependencies only — files land, you restart, and the settings page shows nothing while every route 404s.
With the profile directory $DSH_PROFILE (usually $DSH_HOME/profiles/desktop on desktop), edit $DSH_PROFILE/package.json:
{
"dependencies": {
"dsh-pet": "0.3.8",
"dsh-pet-extras": "0.2.3" // (1) added by pnpm add
},
"dsh": {
"profile": {
"bundles": [
"@deepseek-ai/dsh-base",
// ... other bundles
"dsh-pet",
"dsh-pet-extras" // (2) must be added by hand, or the plugin never loads
]
}
}
}
Restart DSH afterwards. On uninstall, remove both.
Verify it actually loaded
# ok/enabled/minGapSec means loaded; 404 means not loaded (bundles missing, or no restart)
curl http://127.0.0.1:<PORT>/dsh-pet-extras/sight-share
Expected: {"ok":true,"enabled":true,"minGapSec":60}. A 404 / no such route sends you back to the bundles line.
The port changes on every restart — read it from the DSH window or startup log.
This version no longer touches any dsh-pet file
Early 0.1.0/0.1.x builds read screenshotEnabled / agentBridgeEnabled from the dsh-pet config file — keys we had injected into dsh-pet's private config. Official 0.3.8 has none of them, and updates wipe them. That is exactly why switches stayed off for other people.
Now the plugin's config is fully self-contained ($DSH_HOME/dsh-pet/extras-config.json); it does not read dsh-pet's config and does not need dsh-pet/assets/config.jsonc edited.
The only remaining dsh-pet modification is the optional work-status patch — see Work-status bubble vs. pet chat. If it cannot apply, it skips itself and everything else still works.
Configuration
Recommended: change these in DSH settings → Pet extras (switch + number field, applied on save).
The config file lives at $DSH_HOME/dsh-pet/extras-config.json. It is also reachable over HTTP:
# Read the current config
curl http://127.0.0.1:<PORT>/dsh-pet-extras/config
# Change config (send only the fields you want to change)
curl -X PUT http://127.0.0.1:<PORT>/dsh-pet-extras/config \
-H 'content-type: application/json' \
-d '{"screenshotEnabled": true, "sightShareMinGap": 60}'
<PORT> is DSH's web port — the same number in your address bar.
| Key | Type | Default | Description |
|---|---|---|---|
targetSessionId |
string | "" |
Connected session (the authoritative key): the full session id, e.g. session-1d86…. Empty = no delivery and no capture. If the id no longer exists, nothing is delivered — it never silently switches to another session |
agentBridgeTarget |
string | "" |
Legacy: session title. The settings page writes it alongside targetSessionId. It is used only when the config file has no targetSessionId key at all (i.e. upgraded from an old version). Once that key exists the title stops matching entirely — even an empty string counts as you clearing it deliberately, so empty means empty |
screenshotEnabled |
bool | false |
Watch-the-screen master switch: on = timed capture + every shot handed to the connected session (costs tokens per image); off = everything stops. You also need a target session selected below — with none selected nothing is delivered |
sightShareMinGap |
number | 60 |
The only pace knob: minimum seconds between one share and the next (minimum 5). This is a gate, not a metronome — real triggering is also affected by the screenshot helper's 20-second polling alignment, the session's generation time, and occasional skipped ticks. See "Real pacing" below. For a steadier cadence use a multiple of 20 |
screenshotIntervalSec |
number | 20 |
Deprecated: the interval is decided by sightShareMinGap above. The default stays only so old config files still parse; changing it has no effect |
confineToScreen |
bool | true |
The value to push when you want the window kept inside the work area. Only effective when confineManaged is true (set automatically by /dsh-pet-extras/confine) |
confineManaged |
bool | false |
Whether the plugin manages dsh-pet's confineToScreen. Default false = hands off, so installs never touch dsh-pet's config on their own |
screenshotKeep |
number | 60 |
How many screenshot files to keep (1–2000); oldest deleted first |
lookLearnEnabled |
bool | false |
Self-learning for narration (see below) |
lookRulesMaxChars |
number | 1200 |
Stop applying learned rules beyond this length (0–4000) |
| Extra narration rules | file | — | Written in $DSH_HOME/dsh-pet/看图播报要求.md (not a config key; a template is created on first use) |
You need both: ① "Watch the screen" turned on; ② a target session selected. With the switch on but no session selected, nothing is delivered — deliberately, so images never land in an unrelated session.
Note: there is no synchronisation with the
dsh-petsettings page — that claim (and the code behind it) is gone. This plugin's settings are edited only in Settings → Pet extras.
HTTP API
All routes are under /dsh-pet-extras:
| Method | Path | Description |
|---|---|---|
| GET | /ping |
Health check: plugin name, version, DSH_HOME |
| GET | /config |
Current config plus defaults |
| PUT/POST | /config |
Update config (partial keys allowed) |
| GET | /screenshot |
List recent screenshots and switch state |
| POST | /screenshot |
Capture one immediately (spawns the helper) |
| GET | /agent |
Bridge status: target session, queue depth, candidate sessions |
| POST/PUT | /agent |
Body {"sessionId": "..."} to retarget; {"text": "..."} to deliver one message; add "async": true to return immediately and narrate in the background |
| GET | /confine |
Read the keep-on-screen flag |
| PUT/POST | /confine |
Body {"value": true} — pushed to dsh-pet immediately |
| GET | /sights?limit=N |
Recent N sighting records (default 50, max 500) |
| DELETE | /sights |
Clear the sighting log |
| POST | /rate |
Body {"index": 0, "rate": "good"|"bad"|""} — rate one narration |
| GET | /learn |
Self-learning status and learned rules |
| POST | /learn |
Run self-learning once (requires lookLearnEnabled: true) |
How it works
Screenshots
The DSH host process cannot reach desktopCapturer (that is an Electron renderer capability), so the plugin spawns its own minimal Electron process for capture:
- Entry point:
runtime/electron-helper/main.cjs - It starts an HTTP server on
127.0.0.1:<random port>that listens on loopback only, and prints the address to stdout on startup - The host extracts the port from stdout with a regex and then calls
POST /shotto request a capture - PNGs land in
$DSH_HOME/dsh-pet/screenshots/, 60 kept by default
Why not IPC/stdin: in testing, stdin to a spawned Electron process does not work — written lines are never received. Hence loopback HTTP.
Agent bridge
This does not copy context across. It genuinely enters that session:
- Pick a host session according to
agentBridgeTarget(or the automatic rule) - Wait for it to become idle
followupthe pet's message into it- Wait for the turn to finish, then extract the body of
assistant/messageevents - Speak the reply through the pet, one bubble per sentence
The benefit is sharing that session's full context and memory with no duplicate store. The cost is queueing: while that session is busy, the pet has to wait.
How pet replies are distinguished from things you typed: delivered text is prefixed with three zero-width spaces as a fence. When collecting replies the plugin only reads events after that fence, so your own conversation is never read aloud as if it were the pet's answer.
Narration
Narration is not a read-out of detection results; the pet is meant to mutter a remark the way a person would:
- The prompt explicitly forbids report-style openers like "On the screen…" or "In the image…", and forbids reusing the same opener every time
- When unsure, stay vague ("scrolling short videos", "playing a game") — better vague than wrong
- It may have its own reactions and opinions, but must not invent things it cannot see
- Opening interjections are randomised ("took a peek —", "hey,", "huh," …) and only added when the body reads like a dry detection report
There is also a code-level filter: if a sentence contains two or more descriptive words such as "screenshot", "window", "right side", "comments", "progress bar", the whole sentence is dropped. If nothing human survives, it stays silent for that round — better silence than reading out a screen description.
Bubble duration is computed from text length (5.5 characters per second × 2.6 headroom, clamped to 8–45 seconds), not a fixed value. Short lines stay at least 8 s, a typical 20-character line gets 13–14 s, and long ones can reach 45 s.
Real pacing: the configured interval is not the gap between two narrations
Watch interval is a gate (at least this long between two shares), not a metronome (a narration every N seconds, guaranteed). The real gap between two narrations = interval + the following:
| Item | Typical extra | Why |
|---|---|---|
| Screenshot helper polling alignment | 0–20 s | The helper checks for a new image every 20 s, so the actual trigger aligns to the next 20-second tick |
| Session generating the reply | 1–6 s | The session has to actually run a turn before there is anything to say |
| Skipped tick | up to one interval | When a round is judged too-soon or misses the slot, everything shifts back one tick |
So with 120 s configured, expect roughly 2 min 05 s – 2 min 25 s between narrations; occasionally a gap of 3.5 minutes or more (when a tick is skipped). With 60 s, expect about 1 min 05 s – 1 min 25 s.
For the steadiest cadence, set the interval to a multiple of 20 (20 s is the helper's minimum polling granularity) — 60 / 120 / 180 — which minimises jitter.
To watch the real pacing, search
$DSH_HOME/dsh-pet/extras.logfor准备播报or这一轮跳过(too-soon); every line carries a millisecond timestamp.
Self-learning for narration (off by default)
With lookLearnEnabled on, you can rate narrations good/bad via POST /rate. Then POST /learn feeds the rated samples to the model, which summarises "what sounds like a person talking", and writes the result to $DSH_HOME/dsh-pet/look-rules.txt. Later narrations take those rules into account.
It does not learn from the internet, and it cannot change the pet's personality:
- The only input is the local
dsh-pet/视野/index.jsonl; nothing is fetched - The personality is not decided by this plugin: narration comes out of your connected session, so the voice and manner follow the tone you use in that session. The plugin only handles the "don't sound like a manual" layer of wording and cannot touch personality. This is a result of the 2026-10-24 rework — earlier versions hard-coded a personality here (
LOOK_PERSONA_CORE), and it has been removed - If the learned rules contain personality-rewriting content such as "you are / your identity / you are actually", they are rejected outright and the whole rule set is discarded
- Rules longer than
lookRulesMaxCharsare not applied at all (not truncated — a truncated rule is more likely to go wrong)
Compatibility
- Direct dependencies: Node.js built-ins only (
node:fs,node:http,node:child_process, …). No@deepseek-ai/*imports, so version mismatches cannot break it - Host services obtained via
inject:webServer,attachments,agentDefaultModel,agents,llm - Coupling with dsh-pet is HTTP-only:
POST /dsh-pet-7340/broadcast(speak),POST /dsh-pet-7340/anim(animation),GET /dsh-pet-7340/config(read pet config). All of these are public routes in stock dsh-pet — no patched build required - All paths resolve through the
DSH_HOMEenvironment variable; no absolute paths are hard-coded - Window placement is computed live from
screen.getPrimaryDisplay().workArea
System notifications
dsh-pet ships its own system notifications (turn completed / generation failed / output
truncated / permission asked / waiting on you), popped into the corner when the window is
not focused.
If they get in the way, set notificationsEnabled to false in
$DSH_HOME/dsh-pet/main-config.jsonc. That is the master switch — it turns all of them off
at once. This plugin has no notifications of its own and never touches that key.
Privacy and data handling (please read before installing)
This plugin captures your entire primary screen. Screenshots may contain chat logs, password fields, or other people's private information. Please make sure you are comfortable with the following.
What is captured, and where it is stored
- It captures the entire primary display at your configured interval and saves a PNG under
$DSH_HOME/dsh-pet/screenshots/ - By default the file stays on your local disk; the plugin uploads it nowhere
- The number of files rolls over according to
screenshotKeep(60 by default); delete the directory at any time
When data leaves your machine
There is exactly one condition: you turn on screenshotEnabled (off by default).
Once enabled, every new screenshot is handed to the model you configured in DSH for analysis. That means:
- Image content is sent as a request to that model provider (DeepSeek, or whichever provider you configured)
- It uses your own API key; billing and authorisation are your responsibility
- There is no third-party relay and no hidden telemetry endpoint (verify with
grep: the code only talks to local127.0.0.1and the host-providedctx.llm)
If you do not want images to leave your machine, keep screenshotEnabled: false. Screenshots then stay on disk only.
What else it writes
| Path | Contents | Control |
|---|---|---|
$DSH_HOME/dsh-pet/screenshots/*.png |
Raw screenshots | screenshotKeep |
$DSH_HOME/dsh-pet/视野/index.jsonl |
Index of every look: time, image path, size, the session's raw analysis | kept 7 days, DELETE /sights clears the index |
$DSH_HOME/dsh-pet/视野/YYYY-MM-DD/*.png |
The original full-screen captures | folders older than 7 days are removed automatically |
$DSH_HOME/dsh-pet/memory.json |
dsh-pet's chat memory, with sighting conclusions mirrored in | Stops when screenshotEnabled is off |
$DSH_HOME/dsh-pet/extras-config.json |
Plugin config | — |
$DSH_HOME/dsh-pet/look-rules.txt |
Rules produced by self-learning (absent if never used) | lookLearnEnabled |
Narration has exactly one path: the screenshot is handed to the connected session.
| What is sent | What the session gets | Cost | |
|---|---|---|---|
(early sightingInject, removed) |
One sentence | A retelling (knows "watching Douyin", never saw the picture) | Very low |
| Current narration | The screenshot itself | Really sees it, can judge on its own, and it lands in the conversation | ≈76k tokens per share (97% of it is that session's history; the image itself is only 1–2k) |
Two gates before each share: more than sightShareMinGap seconds since the last share, and a local pixel comparison says this screen differs from the last one actually shared. The frame is reduced to a 32×20 grid of average colours; if fewer than 5% of cells changed, the round is skipped entirely — nothing enters the session, no tokens are spent, and the pet says nothing. A moving cursor, a ticking clock, scrolling comments or a creeping progress bar touch too few cells to matter; switching windows or pages changes large areas at once. The comparison lives in lib/screen-diff.js (zero-dependency, reusing the decoder from png-shrink.js) and costs ~100 ms per shot. Thresholds were calibrated on 60 real screenshots plus 8 synthetic scenarios: worst noise 3.1%, smallest real change 9.8%.
Why the "share toggle" is gone: early versions had a
sightShareEnabledflag that only controlled "attach the image or not". Turning it off produced "send a sentence without the picture" — the session was told go look at the screen while seeing nothing, so it could only make things up. After the merge: narration means sharing the screenshot; if you don't want to share, don't narrate — decided solely byscreenshotEnabled.sightShareMinGapstays, because "how often to look" is a knob that genuinely needs tuning.
Migration note: the early
sightingInject/sightingKeep/lookPersonaOwner/sightShareEnabledkeys have been removed, andscreenshotWatchEnabledwent away with the switch merge (2026-10-24: "timed capture" and "timed screen reading" were two switches nobody could keep straight; they are now onescreenshotEnabled— on means capture + share, off means everything stops). Leaving any of them in an old config file causes no errors — the plugin scrubs them on startup.
⚠️ Cost estimate: about 76k tokens per share (see table above). At a 120 s interval and eight hours of continuous use that is roughly 240 shares ≈ 18 million tokens; at 60 s it doubles to ≈ 36 million. This is a real per-hour expense — doubling the interval halves the bill, which beats any amount of image compression.
Permission notes (for security scanners)
- Uses
child_process.spawnto launch an Electron child process as the capture helper. The only file launched is the fixed in-package pathruntime/electron-helper/main.cjs; no external commands or arguments are accepted, so there is no remote code execution surface - The helper opens an HTTP server on
127.0.0.1at a random port — loopback only, not reachable from the LAN - The services requested via
injectare functionally required:attachments(store images),llm(analyse images),agents(inject into sessions),webServer(provide routes) - Does not read secrets from environment variables, does not write to the registry, installs no dependencies, and has no postinstall script
Known issues (being honest — this release is not perfect)
- Desktop mode only (
display: desktop). In web mode there is no Electron process and capture is unavailable - Changes to
sightShareMinGapapply automatically — the helper restarts itself when the interval changes, no manual step needed - The agent bridge is serial. While you are talking to the pet, the target session is very likely busy (it is the one you are using). The pet has to queue. Chat waits up to 90 seconds for idle, then answers "busy"; the screen watch gives up after 10 seconds and yields the turn (captures come every 20 seconds, so waiting 90 seconds would let newer shots overwrite the queued ones). This is a design trade-off, not a bug: pick an idle session, or turn the bridge off, if you want instant replies
- Narration depends on
dsh-pet. Without it (or with a very old version), analysis still runs but nothing can be spoken (pet-unavailable) - This is
0.2.3, the author's first GitHub release, so there may be edge cases I have not thought of. If you hit one, please open an issue and attach$DSH_HOME/dsh-pet/extras.log— it makes debugging much easier - Only tested on Windows with the DSH desktop build. Other platforms should work in principle (all paths go through
DSH_HOME) but have not been tested
Work-status bubble vs. pet chat
dsh-pet delivers its messages in the shape of an ordinary user message (source.id starts with dsh-pet-), so the host cannot tell "the human typed this" from "the pet posted this" — which means pet turns also light up the "thinking / tool call" bubble.
Early 0.1.0/0.1.x builds failed on other people's machines with
anchor-miss:fence: their anchors relied on a constant that only existed in thedsh-petwe had patched locally; stock 0.3.8 has no such constant. It has been rewritten as v2, anchored entirely on stock code, and validated on a clean 0.3.8 copy with 12 install tests + 10 behaviour tests.
This plugin patches that automatically, but only after the dsh-pet version changes: it touches its files exactly once per new version, records what it did in $DSH_HOME/dsh-pet/extras-state.json, and otherwise stays out of the way. Notes:
- It edits the source on disk, so the fix applies on the next DSH start, not immediately
- It backs up first, to
lib/index.js.bak-workstatus-guard(once only — the original is kept) - It only patches the
0.xseries; ifdsh-pet's structure changes so the anchors no longer match, it gives up and logs rather than risk breaking someone else's package - To undo: restore the backup over
lib/index.jsand delete that entry fromextras-state.json
If the auto-patch does not land (check the 工作状态补丁 lines in extras.log), the installed dsh-pet has changed shape — patch it by hand or wait for a plugin update.
This plugin modifies files in dsh-pet — two patches, here is exactly what and why
To be upfront: this plugin modifies one source file of the dsh-pet you installed (node_modules/dsh-pet/lib/index.js). This section lays every change out.
Why it has to: the work-status bubble ("thinking… / calling tools") is computed by dsh-pet itself. It listens to all session events from the host and raises the status by event type. Messages delivered by the pet look like {kind:"user", id:"dsh-pet-<timestamp>"} — structurally identical to something you typed, so dsh-pet cannot tell them apart, and the pet's turns light the bubble as if work were happening. The plugin cannot intercept this: those events go from the host to dsh-pet, and the plugin is not on that chain.
Two patches in total:
- Patch A | work-status guard (4 insertions) — keeps the pet's turns from lighting the "thinking… / calling tools" bubble
- Patch B | agent-bridge hook (2 insertions) — lets what the pet says be answered by your real session
Why Patch B is required: the plugin cannot reach dsh-pet's chatWithPet, so the chat path needs one call site inside dsh-pet. That call site does very little — it POSTs a line of text to the plugin and speaks whatever comes back. With the plugin uninstalled, Patch B not applied, or the switch off, dsh-pet behaves exactly like stock (the hook returns nothing and the original generateChat runs).
Patch A — the exact edits (4 insertions, all anchored on stock 0.3.8 code):
| # | Anchor (present in stock dsh-pet) |
What it does |
|---|---|---|
| 1 | after const dispose = ctx.on("session/event", …) |
declares the petPending / petTurn bookkeeping |
| 2 | the if (type === "user/message") { branch |
recognises a pet delivery (source.kind or source.id starting with dsh-pet) and flags it; clears the flag for the three host-injected kinds (runtime-context, time-context, dsh-mnemon) |
| 3 | the if (type === "turn/start") { branch |
records whether this turn was started by the pet, following the queue semantics |
| 4 | before const next = reduceWorkStatus(…); |
events from the pet's turn return immediately and never enter work status; on turn/end the flags clear and workStatus.clear runs |
Patch B — the exact edits (2 insertions):
| # | Anchor (present in stock dsh-pet) |
What it does |
|---|---|---|
| 1 | before the const chatWithPet = async (petId, text line |
inserts bridgeChatViaExtras(), which POSTs the text to the plugin's /dsh-pet-extras/agent and returns the session's answer; returns nothing when unreachable |
| 2 | before the const generated = await generateChat(…) line |
asks the plugin first; if it handles it, speaks the answer (and writes the bubble itself — otherwise the session replies but the pet stays silent); otherwise falls through to the original generateChat |
Patch B's anchor only matches the head of the signature (const chatWithPet = async (petId, text), because stock 0.3.8 declares (petId, text) with no images parameter. Matching a prefix keeps it compatible with versions that do and do not take images.
What is NOT touched: message delivery, screenshots, shrinking, the sight archive, session contents, and any data file — none of it. The patch only changes "how a turn's events count as work status".
How it stays safe:
- Only acts on the first start after a version change, records it in
$DSH_HOME/dsh-pet/extras-state.json, and stays out of the way while the version is unchanged - The state also records a patch-scheme code (
ws+bridgetoday): upgrading the scheme (for example adding a third patch later) forces one re-run, so nothing is silently skipped just because the version did not change - The two patches are judged independently: if only one is missing, only that one is inserted
- Backs up to
lib/index.js.bak-workstatus-guardfirst (once only, keeping the original) - Only patches the
0.xseries; if a structural change makes an anchor miss, it gives up and logs, never forcing an edit - Verifies every fingerprint after editing (4 for Patch A, 2 for Patch B) and abandons the whole thing if any is missing
- Edits the on-disk source; takes effect on the next DSH start
To undo:
# restore from the backup and drop the state record, then it will not patch again
cp "$DSH_HOME/profiles/desktop/node_modules/dsh-pet/lib/index.js.bak-workstatus-guard" "$DSH_HOME/profiles/desktop/node_modules/dsh-pet/lib/index.js"
Then delete the petWorkStatusPatch entry from $DSH_HOME/dsh-pet/extras-state.json (that file holds only this one entry, so deleting the file also works).
Do not want it patched at all? Uninstall the plugin. If auto-patching does not take effect, extras.log has a dsh-pet 补丁没打上(原因) line explaining why. Common reasons: anchor-miss:* (a newer dsh-pet changed structure) and series-mismatch (not a 0.x release).
Uninstallation
dsh plugin remove dsh-pet-extras
Then restart DSH.
Uninstalling does not delete your data. To clean up fully, remove these (all under $DSH_HOME/dsh-pet/):
| Path | What it is |
|---|---|
screenshots/ |
Screenshot PNGs, potentially large |
视野/ |
Sight archive: originals + the session's raw analysis |
extras-config.json |
Plugin config |
extras.log |
Runtime log |
look-rules.txt |
Learned narration rules |
extras-state.json |
Plugin state (currently: which dsh-pet version was patched) |
memory.json belongs to dsh-pet — do not delete it; it holds the pet's chat memory and has nothing to do with this plugin.
FAQ
I cannot see any settings.
This plugin has its own settings page: Settings → Pet extras (the "Watch the screen" switch, the interval, and which session to connect — provided by dsh-pet-extras; the section is hidden when dsh-pet is not installed).
"Watch the screen" is enabled but the screenshot directory stays empty.
Check two things: (1) the pet's display mode is desktop (web mode has no Electron process); (2) GET /dsh-pet-extras/screenshot reports enabled: true. After changing the interval, the helper must restart — a manual POST /dsh-pet-extras/screenshot triggers that restart immediately.
The pet takes a long time to reply, or just says "busy". The agent bridge is serial: while the target session is running a task, the pet must queue. Chat waits up to 90 seconds for idle; the screen watch waits only 10 seconds (captures come every 20 seconds, so waiting 90 seconds means the shots taken meanwhile get overwritten by newer ones — you end up watching the screen and seeing nothing). For instant replies, point it at an idle session.
I cleared the "connected session", but images still go to the old one.
An early 0.2.0 build had this bug: the config stored both targetSessionId (id) and agentBridgeTarget (title), so clearing only the id let the title silently take over. Fixed — once the targetSessionId key exists (even as an empty string), the title no longer participates in matching. If your config came from an older version (the file has no targetSessionId key at all), the title still acts as a migration fallback; saving once from the settings page writes that key and retires the title for good.
I changed monitors or display scaling and the pet window is off-screen again.
By default the plugin does not manage this. To have it manage, call PUT /dsh-pet-extras/confine with {"value":true} — that also sets confineManaged, after which the plugin pushes the value at startup and on config changes. Until you do, it never touches dsh-pet's confineToScreen.
Narration has gone quiet for a while. That is intentional. The model occasionally emits report-style text ("the screen shows a screenshot with…"), and the plugin drops it rather than reading it aloud. Several silent rounds in a row means the screen content genuinely did not lend itself to a human-sounding remark.
I changed monitors or display scaling and the pet window is off-screen again.
Enable confineToScreen; the plugin pushes it to dsh-pet at startup and whenever the config changes.
Credits
- The pet itself comes from PC2005-cloud/dsh-pet (MIT). This project only calls its public HTTP routes and does not copy its code. The pet being adorable is the original author's work; this plugin merely adds a few things on top
- Host framework: DeepSeek Harness
License
MIT — see LICENSE.
This project is not affiliated with, nor endorsed by, the author of dsh-pet. Copyright of dsh-pet belongs to PC2005-cloud.