# dsh-host-notify

[简体中文](README.md) | English

**Never miss your agent again when you leave the DSH page.** When the model stops and needs you (`ask_user_question` / permission approval / plan review), or a reply finishes in the background, this plugin pings you with a **macOS system notification + sound** — whale icon, persistent banner, and **clicking it raises DeepSeek Harness back to the front**.

What makes it different from browser-half notification plugins: **it runs entirely in the host process** — no renderer, no Service Worker. On Electron desktops, browser notifications posted from hidden/throttled windows get silently dropped (measured: 2 of 9 delivered). Host-side delivery doesn't care about page state at all. That is the entire reason this plugin exists.

## When it fires

| Event | Title | Sound |
|---|---|---|
| Question (`ask_user_question`) | 🔔 Action needed · Question | Glass |
| Approval / plan review | 🔔 Action needed · Approval | Glass |
| Reply finished (`agent/status` → idle) | ✅ Reply finished | Ping |

Subagent events are muted (they are steps of the parent turn). Per-interaction 60s dedup; per-agent 5s dedup for completions.

## Install

```sh
# npm (recommended)
dsh plugin --profile web add dsh-host-notify

# or from GitHub
dsh plugin --profile web add github:huhui-tech/dsh-host-notify
```

Desktop (Electron) app: type `dsh-host-notify` into the in-app plugin manager, then restart the app.

**macOS only** (`"os": ["darwin"]`) — on other platforms the plugin loads as a silent no-op.

## First-run setup (one minute, once)

Notifications are posted by a tiny signed helper app, **DSH Notifier**, vendored inside the package:

1. When the first notification arrives, macOS may ask _"DSH Notifier" would like to send you notifications_ → click **Allow**;
2. Open **System Settings → Notifications → DSH Notifier** and set the style to **Persistent (持续/Alerts)** so banners stay on screen until dismissed;
3. Mute there if you prefer silence.

If DSH Notifier ever fails (rare), the plugin automatically falls back to `osascript` delivery (credited to "Script Editor" — equally reliable, just no icon or click-to-raise).

## How it works

```
ask_user_question / approval / reply finished (host waterfall/status events)
  → dsh-host-notify listener (global, pass-through, key-deduped)
  → vendor/DSH Notifier.app (vendored terminal-notifier binary, adhoc-signed)
  → macOS Notification Center (icon + sound + persistent + click-to-raise)
```

Design notes:

- **Waterfall listeners always pass through**: `user-questions/request` and `approval/request` are waterfall events — listeners only produce side effects and must `next()` the payload untouched. This plugin can never block a question or approval chain; every failure is swallowed silently. Notifications are a bonus, never a dependency.
- **Injection safety**: all delivery args go to `spawn` as an array — zero shell interpolation; title/body clipped to 80 chars.
- **Session labels**: bodies prefer the official `sessionTitle` service's title (you recognize the conversation), falling back to the model name.
- **Dedup**: 60s per interaction, 5s per agent completion — no HMR/reload double-firing.

## Related work

- [chromoany/dsh-notify-me](https://github.com/chromoany/dsh-notify-me) (MIT): product inspiration. Browser-half implementation, richer features (settings UI, actionable buttons); may silently lose notifications from hidden renderer windows on Electron desktops — this plugin is the minimal host-side answer to exactly that layer.
- [bitterSmilezzz/dsh-notify](https://github.com/bitterSmilezzz/dsh-notify) (MIT): also host-side system notifications, with different event coverage and presentation.

## Credits

- The vendored `vendor/DSH Notifier.app` embeds the [julienXX/terminal-notifier](https://github.com/julienXX/terminal-notifier) binary (MIT) — see [NOTICE](NOTICE).

## License

MIT (the plugin itself) · vendored component licenses in [NOTICE](NOTICE).
