Per-workspace DSH conversation status in the Windows system tray, plus native DeepSeek Harness notifications when a conversation needs approval, a choice or free-text input, completes, or fails. Windows 10/11 and DSH Desktop 0.2.3-rc.1+ only. The tray menu and notifications work only after a one-time desktop bridge install (user-level, reversible): it repoints the DSH shortcut at a hidden launcher that injects a hook into DSH's Electron main process through a short-lived loopback Node inspector; without the bridge the plugin only writes state.json. Idle conversations stay out of the tray by default; nine live options are editable in a Tray follower settings page.
Install
# from a prebuilt release tarball
dsh plugin --profile web add "https://github.com/Green233233/dsh_tray_follower/releases/download/v0.2.3/dsh-tray-follower-0.2.3.tgz"
# from GitHub (first run asks for allowBuilds approval — follow the hint, retry)
dsh plugin --profile web add github:Green233233/dsh_tray_follower#path:/packages/dsh-tray-follower
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
A DSH bundle with a Host half and a small web Client half that tracks every
top-level conversation of the running DeepSeek Harness Host, grouped by
workspace, and publishes their status to a state file. The DSH Desktop shell
bridge (see bridge/shell-hook/ in the repository) reads that file to build
the tray icon's right-click menu and to raise native notifications while the
window is hidden or unfocused.
The plugin publishes state only. Notifications are emitted exclusively by the
DSH Desktop shell bridge (in-process Electron Notification, so they appear
under the DeepSeek Harness name) — the plugin never starts an external notifier
process (no PowerShell/WinRT/notify-send). Without the bridge the plugin still
publishes state.json, but no tray section and no OS notifications appear.
- Package name / row:
dsh-tray-follower - Host half:
index.jsexportsConfig,name,inject,apply. - Client half:
client.jsregisters the Settings → Tray follower page (slotsettings.section) that edits the HostConfiglive. - State contract: docs/DESIGN.md §4 / §4.1 — implemented exactly
(
schemaVersion: 2, includes the publishedoptions). - One runtime dependency:
@deepseek-ai/schemastery(the Host Config schema); the Client half imports only React from the browser module table.
What it publishes
%LOCALAPPDATA%\dsh-tray-follower\state.json
(override with DSH_TRAY_FOLLOWER_DIR), written atomically (tmp + rename) with a
≤250 ms debounce:
{
"schemaVersion": 2,
"updatedAt": 1730000000000,
"hostPid": 1234,
"webOrigin": "http://127.0.0.1:61983",
"options": {
"tray": { "showIdle": false, "maxSessionsPerWorkspace": 6, "showHeaderCounts": true, "showFooter": true },
"notifications": { "notifyOnCompleted": true, "notifyOnFailed": true, "notifyOnAttention": true, "suppressWhenFocused": true, "notificationSound": true }
},
"workspaces": [
{
"id": "ws_...",
"title": "dsh_tray_follower",
"path": "C:\\...\\dsh_tray_follower",
"sessions": [
{
"id": "session-...",
"title": "配置 Git 等工程管理",
"status": "running",
"detail": "Running bash",
"updatedAt": 1730000000000,
"startedAt": 1730000000000
}
]
}
],
"unreadEvents": [
{
"id": "evt_...",
"kind": "completed",
"sessionId": "session-...",
"workspaceId": "ws_...",
"title": "Task completed",
"body": "dsh_tray_follower • 配置 Git 等工程管理",
"createdAt": 1730000000000
}
]
}
Notes:
webOrigincomes from the Host'swebApp.getAdvertisedUrl()(falling back towebServer.getOrigin(), thenDSH_WEB_URL); it is""when unknown.workspaceskeeps theworkspaceRegistrydurable order. Conversations that belong to no workspace are published under the synthetic sectionid: "unassigned"instead of being dropped.- Only live top-level conversations are published. Subagent children
(
SessionHeader.origin === 'subagent'ordelegationDepth > 0) are ignored. A conversation that stops being live keeps its finalcompleted/failedstate for five minutes, then disappears from the file. unreadEventskeeps the last 50 events. Ids are deterministic (evt_+ SHA-1 of the logical occurrence), so re-delivered Host events and plugin reloads cannot duplicate a notification on the bridge side.- On unload the file is flushed and left in place; a stale
updatedAtis how the bridge can recognize that the Host is gone.
Status derivation
| status | meaning |
|---|---|
running |
the agent is running, or an active goal keeps continuing between rounds |
waiting-approval |
an approval/request is pending (or approval/asked was logged without its outcome) |
waiting-choice |
ask_user_question with options / plan review is pending |
waiting-input |
free-text ask_user_question is pending |
completed |
the last turn ended completed (or a goal completed) |
failed |
the last turn ended error / max-tokens / blocked, a goal was blocked, or agent/error was recorded and the agent stopped |
idle |
live, nothing pending, no outcome yet (user aborts and crash markers stay idle) |
detail is a short localized line for the menu (Running bash, Waiting for approval: …, Completed, …).
Unread events are emitted for user-relevant stops only:
| kind | source |
|---|---|
completed |
turn/end reason completed, goal complete |
failed |
turn/end reason error/max-tokens/blocked, goal block, agent/error (once the agent stops) |
needs-attention |
pending approval / question that is still pending after 1.2 s |
An immediately answered approval or question never notifies. A conversation
that is following an armed goal suppresses its between-round completed turns
and notifies only when the goal itself completes or blocks.
Notifications
Notifications are not sent by this plugin. The DSH Desktop shell bridge (see
bridge/) reads unreadEvents from state.json, checks the real window
focus/visibility state, and raises a native notification through the Electron
main process (new Notification(...)), which Windows attributes to DeepSeek
Harness itself. This matches the product requirement that notifications be sent
in DSH's own name and that no external PowerShell/WinRT notifier be used.
The bridge suppresses notifications while its window is focused/visible (unless
suppressWhenFocused is turned off), keeps a persistent cursor so events are
never replayed, silently baselines existing events on first run, and ignores
events older than 10 minutes. Kind filters and the sound toggle come from
state.json.options (Settings → Tray follower). When the bridge is not
installed (or injection failed), the Host keeps publishing state and no OS
notification is raised — a deliberately accepted degraded mode.
Settings
All options live in DSH Settings → Tray follower (native page registered by
client.js). They are declared as a .volatile() Host Config schema, so a
change is applied without restarting DSH: the Host polls the live values every
second and republishes state.json.options immediately; the bridge reads them on
its next poll (≤1 s).
| Field | Type / default | Group | Meaning |
|---|---|---|---|
showIdle |
boolean / false |
Tray menu | Also list idle conversations (off = idle conversations never appear in the tray menu) |
maxSessionsPerWorkspace |
number 1–20 / 6 |
Tray menu | Max conversations listed per workspace (overflow shows "还有 N 个会话") |
showHeaderCounts |
boolean / true |
Tray menu | Show running/pending counts in the menu header |
showFooter |
boolean / true |
Tray menu | Show the version footer |
notifyOnCompleted |
boolean / true |
Notifications | Notify when a conversation completes/stops |
notifyOnFailed |
boolean / true |
Notifications | Notify when a conversation fails |
notifyOnAttention |
boolean / true |
Notifications | Notify when an approval/answer is needed |
suppressWhenFocused |
boolean / true |
Notifications | Stay quiet while the DSH window is focused |
notificationSound |
boolean / true |
Notifications | Play the system alert sound with each notification |
Environment overrides are unchanged:
| Variable | Default | Meaning |
|---|---|---|
DSH_TRAY_FOLLOWER_DIR |
%LOCALAPPDATA%\dsh-tray-follower ($XDG_STATE_HOME/dsh-tray-follower, ~/Library/Application Support/... elsewhere) |
data directory holding state.json |
DSH_TRAY_FOLLOWER_LANG |
DSH_LOCALE, LC_ALL, LC_MESSAGES, LANG, JS locale → en |
runtime language for detail (en, zh) |
Bundle contents
package.json name, exports (., ./client, ./locale/*.json, ./icon), files, dsh.bundle.patch, dsh.client
cordis.patch.yml insert row: id dsh-tray-follower, name dsh-tray-follower
index.js Host plugin: Config schema, services, events, timers, state writer
lib/ pure modules (unit-tested with node --test)
client.js Client half: Settings → Tray follower page (settings.section)
locale/en.json meta.title "Tray follower" / meta.description
locale/zh.json meta.title "托盘随航" / meta.description
icon.svg original icon (SVG, < 64 KiB)
test/*.test.mjs node --test suites
Install
Installed by the repository's install step through plugin_manager
install_bundle with the absolute package directory; do not edit the profile's
package.json / cordis.patch.yml by hand. Row metadata
(id: dsh-tray-follower, name: dsh-tray-follower) lives in
cordis.patch.yml.
Develop / verify
# syntax
Get-ChildItem -Recurse packages/dsh-tray-follower -Include *.js,*.mjs | ForEach-Object { node --check $_.FullName }
# unit tests (Node 24 does not expand a positional directory argument; use the glob)
node --test "packages/dsh-tray-follower/test/*.test.mjs"
Known limitations
- Host half cannot know whether the DSH window is focused: focus-based suppression is the bridge's job. Without the bridge there is no tray section and no notification at all (by design — no external notifier process is started). The Settings page itself works without the bridge (it edits the Host Config), but the settings only change tray/notification behaviour once the bridge is installed and DSH was started from the bridge launcher.
- Titles depend on the optional
sessionTitleservice (sessionTitle.get) and itssession/titlelog event; without it the tray falls back to the working directory's last segment. - Only sessions live in the current Host process are tracked; conversations from previous runs are not listed.
- Waiting states from
approval/request/user-questions/requestadditionally depend on being reached by the waterfall: if an earlier answerer claims a request before this plugin's listener runs, the state falls back to the session log (approval/asked,tool/callofask_user_question), which covers tool approvals and questions but not arbitrary non-tool requests. webOrigin/bridging assume the desktop shell can read the data directory written by the Host process (same user profile).- Portable: the Host writes the state contract on every platform; tray menu and notifications require the DSH Desktop bridge (Windows-focused in this repo).
Links
More in this category
xmanrui/dsh-im★ 3118
Connect IM bots to DeepSeek Harness via QR codes or bot credentials (9 channels: Feishu, WeChat, DingTalk, WeCom, QQ, Slack, Telegram, Discord, and WhatsApp).
shaobeichen/dsh-pocket★ 2685
Remote phone access to the DSH Web UI: scan a QR code for LAN or public (cloudflared tunnel) access with real-time sync, a mobile-adaptive layout, and a settings tab.
OpenPetsHQ/openpets#dsh★ 1373
Bridges DeepSeek Harness lifecycle status, errors, and approval requests to a locally running OpenPets desktop companion.
c1overzz/dsh-bili-scroll★ 1150
Let your DSH agent browse Bilibili on its own, read subtitles, write a daily digest, and @ you when it finds something worth your attention.
inclusionAI/Avernet#deepseek-harness-channel-bcn★ 599
Connects DeepSeek Harness to Avernet's Bot Collaboration Network over WebSocket V2, with automatic onboarding, isolated agent sessions, tool-call events, and multi-bot routing tools.
zaofan-make/dsh-qqbot★ 213
QQ official bot channel for DeepSeek Harness (feature-rich fork of tencent-connect/dsh-qqbot): group and private chat with the full agent, sticker library with auto-tagging and AI meme replies, group management (join approval and mute), botplay interactive card events with paginated catalogs, user extension registry (custom slash commands and QQ tools), scheduled wakeups, remote approvals over QQ, and a floating dock web UI.
Community comments
Comments are public GitHub Discussions. Loading them connects to GitHub and Giscus; a GitHub account is required to post.