Browser notification for the DeepSeek Harness Web UI — get a system notification the moment a session finishes running.
dsh-notify is a small, behavior-only Web client plugin for DeepSeek Harness (dsh). When you hand the model a long task and switch to another tab or application, it raises a browser notification the instant the task completes, so you never have to keep checking back.
- 🛎️ System notification on completion — fires exactly when a session's
runningstate flips to idle, titled with the session name and a Chinese completion body. - 👀 No noise while you watch — it stays silent only when you are actively looking at the session that just finished. It notifies when:
- the page is hidden (you switched tab), or
- the browser window lost focus (you switched to another app), or
- the completing session is not the currently selected one.
- 🔁 Per-session dedup — each notification is tagged by session, so a repeat replaces the previous instead of stacking.
- 🔐 Permission asked at the right moment — the permission prompt appears on your first click or keystroke, not while you are away (this is what makes it work in Safari).
- ⚙️ Zero UI — no buttons, no settings screen, no host-side changes. It is one browser row in your profile.
- DeepSeek Harness with the Web UI (
dsh web, ordsh --profile web). - A modern browser. The Web UI runs on
http://127.0.0.1:<port>, which is a secure context, so theNotificationAPI is available. - On macOS, also allow the browser in System Settings → Notifications, otherwise the OS suppresses the banner even when the page has permission.
dsh-notify is an installable bundle. Install it into a profile from npm, a tarball, or straight from this repository.
# from GitHub (this repo)
dsh plugin --profile web add github:you/dsh-notify
# from npm
dsh plugin --profile web add dsh-notify
# from a local checkout or packed tarball
dsh plugin --profile web add ./dsh-notifydsh plugin forwards to pnpm, so any pnpm spec works. Installing a bundle appends it to the profile's dsh.profile.bundles and applies its patch layer; no manual cordis.yml editing is needed.
GitHub installs need no build step. This repository commits its built
lib/, sodsh plugin add github:you/dsh-notifyworks directly, with no install-time build allowance to grant.
Install, then boot the Web UI and grant the notification permission the first time the browser asks (on your first click or keystroke):
dsh --profile webStart a task in any session, switch away, and a system notification appears when the model finishes. There is nothing else to configure.
The plugin injects the runtime's sessions service and subscribes to the session list snapshot — the same fact source the sidebar reads. A small fold keeps each session's previous running bit, records the first observation without notifying, and dispatches one notification per running → idle edge, gated by the "are you looking at this session" rule above.
The browser half is a CJS bundle registered through window.__ModuleLoader__.load, which the harness's client module system scans and serves under /plugins/dsh-notify/client.js. The node half is a no-op host plugin body.
pnpm install # install dev tooling
pnpm build # bundle lib/index.js (node) and lib/client.js (browser)
pnpm typecheck # tsc --noEmit
pnpm test # vitestBuild artifacts are committed under lib/ so GitHub installs work without a build step.
dsh-notify/
├── package.json # dsh.client + dsh.bundle.patch manifests
├── cordis.patch.yml # bundle layer: inserts the dsh-notify row
├── tsdown.config.ts # builds the node half and browser half
├── src/
│ ├── index.ts # node half (no-op host plugin)
│ └── client/
│ ├── index.ts # apply / inject
│ ├── watcher.ts # running→idle edge detection
│ ├── notifier.ts # Notification dispatch + permission bootstrap
│ └── types.ts # minimal local runtime views
└── tests/notify.spec.ts
- Always on — there is no user-facing toggle yet. A settings row (enable/disable, sound, only-when-hidden) is the natural follow-up.
- System notification only — completion does not also produce an in-app toast or a title flash; the sidebar's built-in green "done" dot remains the in-app signal.
- Browser-owned permission — if the user denies the permission prompt, the plugin is disabled for that origin with no in-app recovery.
- No sound — a short chime is deferred.