# Development ## What this plugin does Host-side, OS-level notifications for DeepSeek Harness on Windows. It listens to three existing DSH host events and spawns a tiny self-built .NET 4 helper EXE (`DshToast.exe`) that raises a WinRT `ToastNotification`; toasts persist in Notification Center under "DeepSeek Harness" with the black whale icon. Only Windows is supported. `package.json` declares `"os": ["win32"]` (install-time gate) and `apply()` contains a runtime gate (`isSupportedPlatform(process.platform)`); on other platforms it logs once and registers nothing, so installing the package on Linux/macOS is harmless. ## Events | Event | Condition | Toast | |---|---|---| | `agent/status` | a user-facing **root** agent goes `running → idle` | `任务完成` + session title | | `tools/execute` | tool name is `ask_user_question` | `需要您的输入` + question digest | | `approval/request` | a root agent requests approval | `需要您的审批` + tool/reason | ### Why `tools/execute`, not `tools/result` The DSH tool pipeline is `pre-execute → execute → post-execute → result`. For `ask_user_question` the question modal is presented **while the `execute` body runs** and that call blocks until the human answers; `tools/result` only fires after the tool completed, i.e. after the user already answered. Hook `tools/execute` and always `return next()` — the observer is a waterfall listener and must never block or short-circuit the tool. ### Noise control - `agent/status` fires for **every** agent (root agent, subagents, AgentTeams members, workflow workers). Only user-facing root agents notify: subagent sessions carry `origin: "subagent"` (+ `delegationDepth`) in their session header (`isRootAgent`) and are skipped; forked sessions (`parentSession` without `origin`) stay user-facing. - Agent transitions are tracked per-agent in a `WeakMap` so interleaved events from concurrent agents cannot corrupt each other's state machine. - A per-agent `FINISH_COOLDOWN_MS` (10 s) suppresses completion chatter from rapid consecutive turns / goal continuation rounds. - Subagent questions and approvals (auto-rejected anyway) never ping the user. ## The helper EXE `lib/.dsh-notify/` (source: repo-root `.dsh-notify/`, copied at build): | File | Purpose | |---|---| | `DshToast.exe` | Pre-built WinRT toast raiser (31 KB), csc-compiled, icon embedded via `/win32icon:notify.ico` | | `DshToast.cs` | .NET Framework 4 source; uses `ToastNotificationManager` + minimal COM interop for the shortcut | | `notify.ico` | 9 real frames 16…256 px, transparent corners + black-background white whale | | `whale-black-bg.png` | PNG twin of the icon (documentation / fallback asset) | Why a self-compiled EXE rather than PowerShell: - pwsh cannot load the WinRT Toast projection; its balloon tips neither persist nor can be branded. - Windows PowerShell 5.1 can, but the agent sandbox blocks launching powershell.exe, and its toasts cannot be re-branded or re-iconed. - A self-owned EXE owns its AUMID (`DeepSeekHarness.Notify`), its display name and its embedded icon, so the toast shows exactly "DeepSeek Harness" + the whale icon. If `DshToast.exe` is missing at runtime, `ensureHelper` recompiles it on the fly with the system `csc.exe` (ships with .NET Framework 4.x on every Windows 10/11 — zero extra dependencies); the compile is fire-and-forget, the current round skips its toast, the next one uses the fresh EXE. ### Icon identity chain (why the icon shows) Windows resolves a toast's small icon through the app identity: AUMID → Start-Menu shortcut → embedded icon. Three historical fixes matter: 1. `notify.ico` must contain real small frames (16/20/24/32/… px), otherwise the 256 px frame downscales to a black blob. 2. The Start-Menu shortcut must point its `SetIconLocation` at `notify.ico` and carry the `AppUserModelID` property on disk — `IPersistFile.Save` must run **again after** `IPropertyStore.Commit`, otherwise the property is only in memory (verified empirically: lnk read back `vt=0`). 3. Registry `Software\Classes\AppUserModelId\DeepSeekHarness.Notify` sets `DisplayName` and `IconUri`. 4. Toast XML deliberately carries no ``: `appLogoOverride` renders as a giant circle in Notification Center; the identity channel shows the proper small icon instead. If a **new** toast still lacks the small icon after a fix: Windows caches the toast app identity — re-login or reboot once. Old toasts in Notification Center never change their icon. ## Build TypeScript sources live under `src/`; `lib/` is generated by esbuild and committed so the packaged tree matches the source (`build.mjs`): - bundles `src/index.ts` into `lib/index.js` (ESM, node platform, node built-in modules kept external; the `@deepseek-ai/cordis` import is type-only and erased at build); - copies `.dsh-notify/` → `lib/.dsh-notify/` (the plugin resolves helper paths as `join(__dirname, '.dsh-notify')` — relative to the built entry); - copies `src/types/**` → `lib/types/**`. Zero runtime dependencies; `@deepseek-ai/cordis` is dev-only for typing. ## Test ```sh npm run check # typecheck → build → vitest → smoke (CI runs this) npm run typecheck # tsc --noEmit on src/ + test/ npm run build # regenerate lib/ npm test # vitest run — behavior tests (node env) npm run test:smoke # node test/smoke.mjs — built-artifact loading contract ``` - `test/core.test.ts` — pure functions from `src/core.ts`: `truncate`, `isSupportedPlatform` ('win32' only), `isRootAgent` (subagent origin / delegationDepth / permissive fallbacks), `resolveSessionTitle`, `resolveQuestionText` (multi-question count, aliases, truncation), `resolveApprovalText` (tool/reason variants). - `test/plugin.test.ts` — `apply()` against a fully mocked ctx (no real subprocess, no Windows APIs), platform-adaptive: - Windows: registers the three listeners; drives `agent/status` running→idle → spawns `DshToast.exe` with `任务完成`; subagent completion churn and cooldown produce no toast; `ask_user_question` → `需要您的输入` and the waterfall is always released; subagent questions don't ping; approval → `需要您的审批`; session titles flow into the body. - Non-Windows: `apply()` registers nothing and warns. - `test/smoke.mjs` — imports the **built** `lib/index.js`, asserts the export shape, listener registration, `HELPER_DIR` asset completeness (`DshToast.exe`/`.cs`/`notify.ico`/`whale-black-bg.png`) and, on Windows, one end-to-end mock event → toast argv; on other platforms it asserts the platform guard. ## Release ```sh npm version patch npm run check npm publish --access public git push --follow-tags ``` The `prepack` script rebuilds `lib/` before publish, so the tarball always carries fresh output. The npm `repository` field points back at `github.com/kongdexu/dsh-win-notify` — the awesome-dsh-plugin storefront links the registry package to the listed repo automatically from that field (a hand-written `npm:` key in the listing YAML is rejected). ## Store listing (awesome-dsh-plugin) The plugin market (dsh-market, and the storefront at awesome-dsh-plugin.com) serves `data/plugins/__.yml` entries from the `awesome-dsh-plugin/awesome-dsh-plugin` repo. Submission: ```yaml url: https://github.com/kongdexu/dsh-win-notify name: kongdexu/dsh-win-notify category: notify # not 'ui' — what the plugin does is notify description: en: 'Real Windows toasts ...' zh: '真正的 Windows 系统通知:...' ``` Gate checks: `dsh.bundle` manifest + `cordis.patch.yml` (this repo has them), repo ≥ 1 day old and ≥ 10 commits, `dsh-plugin` GitHub topic, regenerated READMEs via `node scripts/generate-readme.mjs` after `npm ci` in a checkout of the catalog repo.