Introduction¶
On macOS, the common approach to adding notifications to DeepSeek Harness (DSH) is to run an external process executing osascript -e 'display notification ...'. This approach faces two real issues on newer macOS versions: the notification actually belongs to the parent process that initiated the osascript, which does not match DSH’s signature, and the system may silently drop the notification; even if it displays, the icon is not DSH, and clicking it does not return to the session.
The idea behind dsh-macos-notify is to let the DSH application’s own GUI process send notifications. The plugin is maintained by Chanceux-L, is a client-side plugin, implemented via DSH’s dual-half (host + client) structure, aiming for the notification to belong to DSH’s own name and icon, and clicking it jumps back to the corresponding session.
Architecture¶
The plugin is divided into a host side and a client side, connected via a Web interface polling mechanism:
┌─ Host side index.js (Node, inject: ['sessions','webServer']) ──────────┐
│ Monitors top-level session events: turn/end (completed/error), │
│ ask_user_question, approval/asked │
│ → Write to memory queue (limit 100 items) │
│ → ctx.webServer.register exposes: │
│ GET /macos-notify/now → Current latest notification id │
│ GET /macos-notify/pending?since=N → Incremental pull queue │
│ Sub-agents / forked sessions / team member sessions have │
│ parentSession / delegationDepth headers and are skipped directly— │
│ only top-level sessions visible to the user trigger notifications. │
└──────────────────────────────────────────────────────────────────────┘
│ Polling (2 seconds)
┌─ Client side client.js (Web GUI page, inject: ['uiWorkspace']) ───────┐
│ Registered via window.__ModuleLoader__.load, fetches queue every 2s │
│ → new Notification() sends it—within the Electron rendering process,│
│ this is the DSH application's own macOS native notification │
│ → onclick calls uiWorkspace.openSession(sessionId) to jump session + │
│ window.focus() │
│ Fallback when Notification unavailable/rejected: in-page AudioContext │
│ beep + title flash │
└──────────────────────────────────────────────────────────────────────┘
│ Page closed (no polling for > clientQuietMs)
┌─ Host Fallback Channel ──────────────────────────────────────────────┐
│ afplay plays /System/Library/Sounds system sounds │
│ When input is blocked (questions/approvals), additionally │
│ open -a "DeepSeek Harness" brings up the window │
└──────────────────────────────────────────────────────────────────────┘
The host side is responsible for monitoring session events and maintaining the queue, while the client side executes new Notification() within the rendering process. This is exactly why the notification can be attributed to the DSH signature. Sub-agents, forked sessions, and team member sessions are skipped; only top-level sessions directly visible to the user trigger notifications.
Core Features¶
- Turn Completion / Failure Notification: Dispatched based on
reason.kindonturn/end. Failure includes an error message (truncated to 120 characters) and displays the turn duration. - Question Notification: Notifies the first question (including header) when the Agent calls
ask_user_question. For multiple questions, it displays(+N more). - Approval Notification: Notifies the tool name and reason when
approval/asked. - Click to Jump Session:
onclick→uiWorkspace.openSession(sessionId)+window.focus(). - No GUI Fallback: If the page is closed and has not polled for more than
clientQuietMs, the host directly usesafplayto play system sounds; for input blocking (questions/approvals), it also usesopen -ato bring up the DSH window. - Smart Filtering: Skips sub-agents, forks, and team member sessions; turns shorter than
minTurnSecondsdo not trigger notifications; expired notifications older than 10 minutes are not resent.
All behaviors can be disabled or adjusted via configuration, see configuration options below.
Installation and Enablement¶
It is recommended to install via the DSH CLI with the github: source code form:
dsh plugin --profile web add github:Chanceux-L/dsh-macos-notify
You can also manually link or clone the directory into the profile’s node_modules:
git clone https://github.com/Chanceux-L/dsh-macos-notify \
~/.dsh/profiles/desktop/node_modules/dsh-macos-notify
# Or use a symbolic link during development:
ln -s /path/to/dsh-macos-notify ~/.dsh/profiles/desktop/node_modules/dsh-macos-notify
The plugin registers via cordis.patch.yml using the insert method (id: macos-notify), so there is no need to manually modify configuration files. After restarting DSH, the first notification will trigger the system notification permission popup; just allow it.
Configuration Options¶
Override under config: in the profile patch; default values are defined in DEFAULTS in index.js.
| Key | Type | Default | Description |
|---|---|---|---|
onCompleted |
boolean | true |
Notify when a turn completes |
onError |
boolean | true |
Notify when a turn fails |
onQuestion |
boolean | true |
Notify when the Agent asks a question |
onApproval |
boolean | true |
Notify when tool approval is needed |
minTurnSeconds |
number | 5 |
Do not notify if turn is shorter than this many seconds |
titlePrefix |
string | "DSH" |
Notification title prefix, format Prefix · Session Title |
soundCompleted |
string | "Glass" |
Host fallback sound effect (filename under /System/Library/Sounds, without .aiff; '' to disable) |
soundQuestion |
string | "Ping" |
Question fallback sound effect |
soundApproval |
string | "Funk" |
Approval fallback sound effect |
soundError |
string | "Basso" |
Failure fallback sound effect |
activateOnInput |
boolean | true |
If no GUI and input blocking, open -a brings up DSH window |
clientQuietMs |
number | 15000 |
If client hasn’t polled for more than this many ms, host enables fallback channel |
debug |
boolean | false |
Append delivery diagnostics to ~/.dsh/macos-notify.log |
Configuration example:
- insert:
- id: macos-notify
name: 'dsh-macos-notify'
config:
titlePrefix: DSH
minTurnSeconds: 10
soundCompleted: Glass
Typical Usage¶
After installing and restarting DSH with the command above, the minimum viable configuration is the default values, requiring no extra patch. Below are examples for common adjustments.
To prevent rapid consecutive turns from spamming the screen, raise the threshold:
- insert:
- id: macos-notify
config:
minTurnSeconds: 30
If the page is always open and you don’t need host fallback sounds, you can turn off the corresponding switches:
- insert:
- id: macos-notify
config:
onCompleted: true
onError: true
onQuestion: true
onApproval: true
clientQuietMs: 600000
activateOnInput: false
To troubleshoot, enable debug, and delivery diagnostics will be appended to ~/.dsh/macos-notify.log:
- insert:
- id: macos-notify
config:
debug: true
Comparison with Existing Plugins¶
| dsh-macos-notify (This Plugin) | CrombastiC/dsh-macos-notify | jypjypjypjyp/dsh-notifier | dsh-complete-notify | |
|---|---|---|---|---|
| Notification Channel | App’s own GUI process (Web Notification) | External osascript process |
External osascript process |
External osascript process |
| macOS Signature Check | Passed, attributed to DSH itself | May be silently dropped | May be silently dropped | May be silently dropped |
| Notification Icon Attribution | DeepSeek Harness | Other app identity | Other app identity | Other app identity |
| Click to Jump Session | ✅ uiWorkspace.openSession |
❌ | ❌ | ❌ |
| Page Closed Fallback | ✅ afplay + open -a | Partial | Partial | Partial |
| Question/Approval/Failure Categorized Notifications | ✅ | Partial | Partial | Partial |
Applicable Scenarios and Notes¶
Suitable for developers who use the DSH graphical interface on macOS daily and want to receive clickable notifications when turns end, Agent asks questions, or approval is needed. The plugin itself only handles notification delivery and does not change session logic.
Points to note:
- The plugin runs with the permissions of the current dsh process; you should check the source code and license before installing (this repository is MIT).
- The plugin relies on the Web Notification API in the Electron rendering process; it requires macOS to allow DSH to send notifications; the first trigger will pop a system permission box.
- Dock icon badge count requires DSH official to expose
app.dock.setBadgeCountIPC in the Electron main process; the plugin layer cannot implement it. - Cross-workspace session jumping is not yet implemented;
uiWorkspace.openSessioncurrently only supports navigation within the current workspace. - Host fallback sounds rely on system sound files under
/System/Library/Sounds.
Summary¶
dsh-macos-notify solves a specific problem: making DSH’s notifications truly belong to the application itself on macOS, and being clickable back to the session. It uses a dual-half plugin structure to tie together event listening, queuing, and native notifications, paired with filtering, fallback, and a full set of configuration. If you need to add notifications to DSH on macOS, this is a ready-to-deploy solution.
Directory Page: https://www.skillhub.cn/plugins/Chanceux-L/dsh-macos-notify
GitHub: https://github.com/Chanceux-L/dsh-macos-notify