dsh-macos-notify: Send macOS native notifications using DSH's identity

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.kind on turn/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 uses afplay to play system sounds; for input blocking (questions/approvals), it also uses open -a to bring up the DSH window.
  • Smart Filtering: Skips sub-agents, forks, and team member sessions; turns shorter than minTurnSeconds do 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.setBadgeCount IPC in the Electron main process; the plugin layer cannot implement it.
  • Cross-workspace session jumping is not yet implemented; uiWorkspace.openSession currently 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

羽毛球分组比赛记分
小程序二维码

欢迎使用《羽毛球分组比赛记分》微信小程序

Xiaoye