Skip to content

feat(notifications): notify completed turns and waiting requests from Settings - #687

Open
aEboli wants to merge 1 commit into
dataelement:mainfrom
aEboli:feat/desktop-notifications
Open

aEboli wants to merge 1 commit into
dataelement:mainfrom
aEboli:feat/desktop-notifications

Conversation

@aEboli

@aEboli aEboli commented Oct 12, 2026

Copy link
Copy Markdown

作用 / What this does

DSH Desktop only showed a finished turn while the window was visible. This adds one operating-system notification for the news a background window would otherwise miss, configurable from 设置 → 通用 / Settings → General:

Row Values
轮次完成通知 / Turn completion notifications 仅在未聚焦时 / Only when unfocused (default), 始终 / Always, 从不 / Never
需要回答时提醒 / Notify when a turn needs an answer on by default — a turn waiting for an answer or a permission decision
通知提示音 / Notification sound on by default

Clicking a notice returns to the application (restores a minimized window and focuses it, or reopens the workspace when the Harness is not on screen).

How it is put together

  • packages/dsh-desktop-notifications (new desktop plugin, mounted by build/dsh-desktop.patch.yml) — the browser half observes Harness state: sessions.list for running-to-idle transitions and uiSession.sessionStatus for a newly published pendingInteraction. Only direct conversations notify; addressed children and subagent rows stay silent, and a reconnect republishing an idle row never replays history. The three rows register on the existing settings.general.item slot, with zh/en copy in ctx.locale.register.
  • src/main/turn-notification.ts (new module) — the shell owns focus observation, payload validation (bounded, untrusted renderer copy), the native Notification, and the click that activates the window. A background-only notice is suppressed while the window is focused, decided here rather than from a renderer boolean.
  • src/preload/index.ts — two calls on the existing dshDesktop bridge: notifyTurnComplete(payload) and clearTurnNotification(). Both main-process handlers require the trusted main window and main frame (assertTrustedMainWindowEvent).
  • Preferences live in renderer localStorage, which the desktop already persists into the profile's desktop-storage.json, so they survive restarts and profile switches without depending on a Harness settings document the desktop cannot write.

Verification

  • npm run typecheck — clean.
  • npx vitest run test/turn-notifications.test.ts test/turn-notification-main.test.ts test/desktop-plugin-closure.test.ts — 24 tests pass:
    • the plugin's registrations, dictionary parity, stored-preference adoption/writes, style lifetime;
    • the policy's mode/focus matrix, child and subagent filtering, no replay on republish, one notice per waiting request, dispose, failing sink;
    • the shell module's payload validation and fallback copy, focus suppression, replacement, click activation with inert retired clicks, unsupported platform, and failing platform paths;
    • the plugin-closure test that pins the new package into the patch, the @deepseek-ai/dsh dependency patch, and package.json.
  • npx patch-package --error-on-fail — every patch applies, including the edited @deepseek-ai+dsh+0.2.0-rc.2.patch.
  • Manual check on a packaged/preview build: start a turn, switch to another application, and confirm one notification arrives with the app icon; click it and the window returns. Then repeat with 始终 / Never, and with a permission prompt pending.

Not verified on this machine: test/host-plugin-sources.test.ts fails with ENOTDIR in a temp directory on Windows both with and without this change (a plain Node probe of the same mkdir succeeds), so it is environmental rather than caused by this branch.

Relationship to #674

#674 routes the web Notification API through the main process (bundle icon, click activation). This change adds the capability that decides when a notice is wanted and where it is configured; its shell bridge is intentionally separate so it works with today's main. If #674 lands, the renderer side here can either keep using dshDesktop.notifyTurnComplete or be re-pointed at the bridged window.Notification without touching the rows or the policy.

Notes for review

  • The feature is a desktop capability only: without window.dshDesktop the rows keep working and nothing is presented.
  • The patch edit only appends the new package to the @deepseek-ai/dsh dependency closure, which the plugin-closure test requires.

… Settings

DSH Desktop only showed a finished turn while the user was looking at the
window. The new dsh-desktop-notifications plugin observes the Harness sessions
it is composed with and asks the shell for one system notification when a turn
finishes, or when a turn starts waiting for an answer or a permission decision;
the shell presents it only while the conversation window is in the background.

Settings -> General gains three rows above the version row:
  - Turn completion notifications: Only when unfocused (default), Always, Never
  - Notify when a turn needs an answer
  - Notification sound

Preferences live in desktop storage (renderer localStorage -> the profile's
desktop-storage.json), so they survive restarts and profile switches without a
Harness settings document the desktop cannot write. The renderer only reports
Harness session news: src/main/turn-notification.ts owns focus observation,
payload validation, the native notice, and the click that returns to the app,
and speaks to the page through the existing dshDesktop preload bridge.

Verified with: npm run typecheck; npx vitest run test/turn-notifications.test.ts
test/turn-notification-main.test.ts test/desktop-plugin-closure.test.ts (24
tests); npx patch-package --error-on-fail.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant