@ag-dsh/dsh-web-notify
August 15, 2026 · View on GitHub
@ag-dsh/dsh-web-notify
DSH web notification plugin: when a conversation turn completes, the assistant asks you a question, an approval is required, or a turn errors, it alerts you through the browser's native system notifications (Web Notifications API) — even when the tab is in the background, the window is minimized or unfocused, the notification still appears in the system notification center (macOS Notification Center / Windows Action Center). Supports Chrome / Edge / Firefox / Safari.
Installation
Using the most common web profile as an example:
# Install
dsh plugin --profile web add @ag-dsh/dsh-web-notify
# ⚠️ You must restart dsh after install for the plugin to load:
# exit the current dsh process first, then start it again
dsh --profile web
dsh plugin addonly installs the package and registers it indsh.profile.bundles(the profile'spackage.json); the running process composes its configuration tree at startup and hot reload does not watch the bundles list, so a restart is required.
Alternatively, skip the bundle mechanism and add the plugin row directly to the profile's
cordis.yml / cordis.patch.yml (hot-applied by HMR, no restart needed):
- id: web-notify
name: '@ag-dsh/dsh-web-notify'
The plugin row (host half) is only a carrier for the client bundle: dsh-client-modules
scans loaded packages that declare dsh.client and serves their ./client export
(lib/client.js) as the browser bundle. All functionality runs in the browser; the host
half has no business logic.
Host services used
| Service | Dependency | Description |
|---|---|---|
slots (client) | ctx.get optional | Registers the invisible shell.overlay background-session engine, the settings.plugin.item config card, and the conversation.session.header.utilities observer |
sessions (client) | ctx.get optional | Opens the target session when a notification is clicked |
timer (client) | inject hard | Auto-closes notifications after 12 seconds |
Usage
- After install and restart, open Settings → Plugins → Plugin config and find
the Web 通知 card (
settings.plugin.item). The default mode is always (notify whether focused or not) and the beep is on by default. - Click 「开启通知」 in the config page and allow the browser permission prompt (Firefox / Safari require this button gesture).
- System notifications appear for (in 「始终」 mode they also fire while focused;
switch back to 「仅后台」 to reduce interruptions):
- 对话完成 (turn complete): any session's turn ended;
- 需要你的回答 (answer needed): the assistant called
ask_user_question(including plan-review confirmations); - 需要授权 (approval needed): a pending approval request;
- 对话出错 (turn error): the open session reported an error.
- Clicking a notification focuses the window and opens the target session.
- Config page controls:
- 通知模式: choose one of 仅后台 (notify when the window is unfocused), 始终 (also notify while focused, default), 关闭 (disabled);
- 提示音: on/off toggle (on by default; event-dependent: 1 beep on completion, 2 on question/approval, 3 on error), plus a 「🎵 试听」 button that plays all three beeps in sequence;
- 通知权限: shows the current state (已允许 / not enabled / 被拒绝); if denied,
allow notifications for
127.0.0.1(or your deployment domain) manually in the browser's site settings.
Config
None.
Model-visible text
The following is a stable contract: notification titles and bodies are generated by
notificationSpec in src/core.ts; changes must update this table and the tests
(tests/index.spec.ts).
| Event | Title | Body (priority order) |
|---|---|---|
| Turn complete | 对话完成 | 「[session title] last reply summary」(truncated to 220 chars) → Session title → 「助手已回复,点击查看」 |
| Answer needed | 需要你的回答 (counts questions as 「(N 个问题)」 when multiple) | Question text (truncated to 220 chars) → Session title → 「请回答助手的问题」 |
| Approval needed | 需要授权 (marks subagent sessions as 「(子代理)」) | 「[session title] tool: reason」 → Session title → 「需要你的授权」 |
| Turn error | 对话出错 | Error message (truncated to 160 chars) → 「对话运行出错」 |
Notification bodies collapse consecutive whitespace (including newlines) before truncation for single-line readability; approval notifications carry the initiating session title plus the tool/reason, question notifications carry the question count and the first question's text, so you can decide from the notification center whether action is needed.
Pill texts (NOTIFY_MODE_LABELS in src/core.ts): 仅后台 / 始终 / 关闭;
button texts: 开启通知 / 通知被拒 / 通知不可用.
Behavior
- Signal source: subscribes directly to DSH Web's existing session-state snapshots
(
useSessions/useSession, pushed over the websocket, no polling); no Host RPC, no extra network requests. - Coverage: the
shell.overlayengine observes all sessions (including background sessions/subagents) forrunningandpendingInteractionedges; theconversation.session.header.utilitiesengine provides open-session rich notifications (question text, approval tool/reason, error message). That slot sits outside the composer takeover region, so observation survives question/approval cards. - False-positive guard: every observer seeds its baseline on first observation
(page load, session switch, or a session joining the list never mis-notifies);
notification
tag = dsh-notify:<kind>:<sessionId>replaces same-kind notifications for the same session instead of stacking. - Browser compatibility:
Notificationis natively supported by Chrome / Edge / Firefox / Safari;127.0.0.1/localhostis a secure context and can request permission normally. - Reversible side effects: all Slot registrations (observer engines and the
settings page) are cleaned up with the plugin fiber; timers use
ctx.timeout(thetimerservice); HMR / hot-reload safe.
Known Limitations and Deferred Work
- No notifications when the browser is fully closed or the tab is closed: this plugin uses in-page native notifications without server push; receiving notifications with the browser closed requires Web Push (FCM for Chrome, APNs for Safari), which a local 127.0.0.1 service cannot integrate — out of scope.
- Mode and sound are in-memory state: they reset to 「始终 / on」 after a page reload; the browser-level permission itself is persisted by the browser and unaffected.
- Error notifications cover the open session only: when a background session errors,
its turn end still raises a 「对话完成」 notification (
runningfalling back is the shared signal). - Multiple tabs each fire their own notifications; the
tagprevents stacking. - Transient snapshots while the session list loads:
itemsmay be briefly empty before the first list build — handled defensively (empty array), no crash or mis-notification. - Beeps are subject to the browser autoplay policy: audio can only start after at least one click on any button in the settings page (e.g. 「🎵 试听」); otherwise it degrades silently.