Sessions & Messages

dsh-pet-extras

amaji61/dsh-pet-extras · ★ 0

amaji61

DSH 桌宠扩展:让桌宠看你的屏幕、把画面交给一条真实会话来判断,再用桌宠的声音说出来。零第三方依赖。

License
MIT
Added
2026-10-30
Maintenance
active
Install kind
github
Manifest details
Manifest status
Declared
Manifest path
package.json#dsh
Manifest checked
2026-10-30T09:38:32.147Z
Lifecycle scripts
None found

Static manifest checks report declarations only; they are not compatibility or security review.

安装

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

Source README · body: en · Read Chinese README

Table of contents
  1. ⚠️ Read this first: it captures your entire screen
  2. The agent bridge: this plugin's foundation, not an optional extra
  3. How this differs from dsh-pet's built-in chat
  4. Features
  5. Settings page
  6. Install
  7. Desktop app: one-click (recommended, no JSON editing)
  8. Manual install (pnpm add / offline tgz): two places must be edited
  9. Verify it actually loaded
  10. This version no longer touches any dsh-pet file
  11. 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.
  12. Configuration
  13. HTTP API
  14. How it works
  15. Screenshots
  16. Agent bridge
  17. Narration
  18. Self-learning for narration (off by default)
  19. Compatibility
  20. System notifications
  21. Privacy and data handling (please read before installing)
  22. What is captured, and where it is stored
  23. When data leaves your machine
  24. What else it writes
  25. Permission notes (for security scanners)
  26. Known issues (being honest — this release is not perfect)
  27. Work-status bubble vs. pet chat
  28. This plugin modifies files in dsh-pet — two patches, here is exactly what and why
  29. Uninstallation
  30. FAQ
  31. Credits
  32. 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-pet author. It is at 0.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-pet settings page" — no such thing exists. The plugin's config lives only in its own extras-config.json and 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.

  1. Settings → Plugins → Add plugin
  2. 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
  3. 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-pet settings 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 /shot to 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:

  1. Pick a host session according to agentBridgeTarget (or the automatic rule)
  2. Wait for it to become idle
  3. followup the pet's message into it
  4. Wait for the turn to finish, then extract the body of assistant/message events
  5. 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.log for 准备播报 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 lookRulesMaxChars are 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_HOME environment 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 local 127.0.0.1 and the host-provided ctx.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 sightShareEnabled flag 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 by screenshotEnabled. sightShareMinGap stays, because "how often to look" is a knob that genuinely needs tuning.

Migration note: the early sightingInject / sightingKeep / lookPersonaOwner / sightShareEnabled keys have been removed, and screenshotWatchEnabled went away with the switch merge (2026-10-24: "timed capture" and "timed screen reading" were two switches nobody could keep straight; they are now one screenshotEnabled — 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.spawn to launch an Electron child process as the capture helper. The only file launched is the fixed in-package path runtime/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.1 at a random port — loopback only, not reachable from the LAN
  • The services requested via inject are 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 sightShareMinGap apply 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 the dsh-pet we 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.x series; if dsh-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.js and delete that entry from extras-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+bridge today): 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-guard first (once only, keeping the original)
  • Only patches the 0.x series; 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.