dsh-thinking-notifier
UI & Experience6-debug-6/dsh-thinking-notifier
A popup plugin that shows when DeepSeek has finished thinking or is requesting permission during split-screen, with always-on-top desktop notifications.
- dsh-plugin
Verification and compatibility
This section shows evidence collected by the catalog. Undeclared information is labeled as unknown.
- Current-version compatibility
- Verified on the catalog Harness version
- Declared Harness range
- Not declared
- Declared platforms
- Not declared
- Profiles
- web
- Build approval
- Unknown
- Permissions
- Not declared
- External services
- Not declared
- Telemetry
- Unknown
This is not a security endorsement. Review source, permissions, and configuration before installing.
View evidence and scope
Verification covers only the named source, version, and Harness environment. It does not guarantee future compatibility.
github:6-debug-6/dsh-thinking-notifier#d97ab09f21568e22b47e02d036ef678212d2d137- The manifest and static structure passed checks; a runtime load check has not completed.
README
dsh-thinking-notifier
A DeepSeek Harness (DSH) plugin that shows a small always-on-top desktop popup in the bottom-right corner of your screen. It mirrors the AI's thinking status in real time and alerts you when the AI requests permission, waits for a choice from you, or finishes thinking — even when you are working in other apps, browsers, or virtual desktops.
中文说明见 README.zh.md
Features
| State | Popup | Sound |
|---|---|---|
| AI is thinking | DeepSeek-blue pulsing dot + AI 正在思考… + elapsed time. Auto-collapses to a 25 px slim right-edge tab after 5 s; hover or click the tab to expand it again | none |
| AI waits for your choice | Violet pulsing dot + AI 正在等待你的选择 + the question heading, the first question's option labels (选项:A / B, (可多选) for multi-select, (共 N 问) when several questions arrive at once) and how long it has been waiting. Stays expanded until you answer | system Question |
| AI requests permission | Amber pulsing dot + AI 请求权限 + session/tool name. Stays expanded until you decide | system Exclamation |
| AI finished thinking | Green dot + AI 已完成思考, auto-hides after 12 s | system Asterisk |
| Idle | Hidden | none |
- DeepSeek-style dark card (
#1E2235/#4D6BFE). - Always-on-top frameless window, bottom-right, visible over any application.
- Auto-collapses into a 25 px slim right-edge tab after 5 s while the AI is thinking, so it stays visible without blocking desktop controls.
- Hover or click the edge tab to expand it manually; new turns, questions, permission requests, and completions expand it automatically.
- Click the X button to dismiss the current popup; the next new turn, question, permission request, or completion will show it again.
- Questions are shown with their options, which is enough to know what is being decided; the answer itself is still given in the DSH Web UI (the popup is read-only by design).
- Multiple sessions are summarized automatically.
- The popup process stays alive while DSH runs; it exits ~15 s after the status endpoint becomes unreachable.
- No browser extension required; the popup is a native desktop window.
How it works
DSH process
└─ lib/index.js (host plugin)
├─ listens to session/event: turn/start, turn/end, approval/asked, approval/decided, session/title
├─ rebuilds existing session state from ctx.sessions.list() at startup
│ (a session whose log ends inside an open turn is treated as not running)
├─ wraps ctx.userQuestions.ask to observe ask_user_question
│ (that state produces no session event, and a second UI provider cannot be registered)
├─ serves a local status JSON at http://127.0.0.1:7389/status
└─ launches the desktop popup process
├─ Windows: desktop-popup.ps1 (PowerShell + WPF)
└─ macOS/Linux: desktop-popup.py (Python 3 + tkinter)
Desktop popup
├─ polls /status every second
├─ renders the DeepSeek-style card (question/pending/running/done/idle)
├─ plays a system sound on question/pending/done transitions
├─ auto-collapses to the right edge while running; stays expanded while waiting for you
└─ hides while idle, keeps running in the background
Status JSON
GET http://127.0.0.1:7389/status returns:
{
"ok": true,
"mode": "question",
"statusText": "AI 正在等待你的选择",
"sessionText": "Choose Mode",
"detailText": "选项:样式 A / 样式 B(可多选)",
"timeText": "已等待 00:12",
"pendingKind": "question",
"pendingTool": null,
"runningSince": null,
"doneAt": null,
"questionCount": 1,
"questionSince": 1791374946369,
"appUrl": "http://127.0.0.1:3080",
"now": 1791374946369
}
mode priority: pending (an approval is waiting) > question (a question is waiting) > running > done (shown for 12 s) > idle.
pendingKind is approval or question. timeText is the thinking timer while running and the waiting time while question; detailText carries the first question's options.
Installation
From a local checkout
cd dsh-thinking-notifier
dsh plugin --profile web add .
# Restart DSH web so the host plugin is loaded
dsh web
From npm (once published)
dsh plugin --profile web add dsh-thinking-notifier
dsh web
Uninstall
dsh plugin --profile web remove dsh-thinking-notifier
Configuration
| Environment variable | Default | Description |
|---|---|---|
DSH_THINKING_NOTIFIER_PORT | 7389 | Local status server port. If busy, the next 10 ports are tried. |
DSH_THINKING_NOTIFIER_NO_POPUP | unset | Set to 1 to run the status server without launching the desktop popup (headless, tests). |
Requirements
- DeepSeek Harness Web UI (
dsh web) - Node.js
^22.19.0 || >=24.0.0 - Windows: Windows 10/11 with PowerShell 5.1+ (built in)
- macOS/Linux: Python 3 with
tkinter
Verification status
Being explicit about what has actually been tested, so nobody is surprised:
| Area | Status |
|---|---|
| Thinking / done states, popup geometry, sounds, single instance | verified on Windows 10/11 (PowerShell 5.1 + WPF) |
Question state (ask_user_question) | verified end to end in a live DSH session (a real question drove the popup into the waiting state, and it cleared when the answer arrived), against the real ctx.userQuestions service in a cordis context, and by the smoke test |
| Permission alert (amber) | verified only with the mock status service. On a Windows DSH install without a confining sandbox, an out-of-workspace write is not intercepted, so no approval is raised by the runtime; the request only appears when a tool call explicitly asks for a sandbox_permissions escalation or a pre-execute hook returns ask |
desktop-popup.py (macOS/Linux) | never run on real hardware — syntax-checked only |
Popup process stacking after repeated dsh web restarts | the launcher kills stale popups for its own port before starting a new one; verified for a single restart cycle |
Development
# status-machine smoke test (fake cordis context, no popup, port 17389)
npm test
# render the popup against a mock status service, on a spare port
node tools/mock-status.mjs question --port 17389
powershell -NoProfile -ExecutionPolicy Bypass -File desktop-popup.ps1 -Port 17389 # Windows
python3 desktop-popup.py --port 17389 # macOS/Linux
Use a spare port for popup experiments: the real popup on 7389 is left alone, because the plugin only cleans up popup processes belonging to its own port.
Project structure
dsh-thinking-notifier/
├── lib/
│ └── index.js # host plugin: state machine + status service + popup launcher
├── desktop-popup.ps1 # Windows native popup (PowerShell + WPF)
├── desktop-popup.py # macOS/Linux native popup (Python 3 + tkinter)
├── cordis.patch.yml # DSH bundle patch for persistent mounting
├── test/
│ └── status-smoke.mjs # status-machine smoke test
├── tools/
│ └── mock-status.mjs # mock status service for offline popup testing
├── package.json # npm package + dsh.bundle declaration
├── README.md # English readme
├── README.zh.md # Chinese readme
├── CHANGELOG.md
├── CONTRIBUTING.md
└── LICENSE
Troubleshooting
Debug logs are written to:
~/.dsh/tn-plugin.log— host plugin lifecycle, popup launcher, question watcher.~/.dsh/tn-launcher.log— PID of the popup process created by the launcher.~/.dsh/tn-popup-debug.log— popup script lifecycle and polling errors.
If the popup does not appear after restarting dsh web, check those logs first.
Note that popup launcher exit: code=0 in tn-plugin.log is expected: the plugin spawns a one-shot launcher which starts the real popup process and then exits.