dsh-messager 0.1.5 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +169 -45
- package/README.md +145 -146
- package/cordis.patch.yml +13 -0
- package/lib/channels/dingtalk.d.ts +38 -0
- package/lib/channels/dingtalk.d.ts.map +1 -0
- package/lib/channels/dingtalk.js +60 -0
- package/lib/channels/dingtalk.js.map +1 -0
- package/lib/channels/discord.d.ts +28 -0
- package/lib/channels/discord.d.ts.map +1 -0
- package/lib/channels/discord.js +44 -0
- package/lib/channels/discord.js.map +1 -0
- package/lib/channels/telegram.d.ts +31 -0
- package/lib/channels/telegram.d.ts.map +1 -0
- package/lib/channels/telegram.js +52 -0
- package/lib/channels/telegram.js.map +1 -0
- package/lib/channels/wecom.d.ts +34 -0
- package/lib/channels/wecom.d.ts.map +1 -0
- package/lib/channels/wecom.js +53 -0
- package/lib/channels/wecom.js.map +1 -0
- package/lib/client.js +391 -54
- package/lib/client.js.map +1 -1
- package/lib/config.d.ts +46 -0
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +27 -0
- package/lib/config.js.map +1 -1
- package/lib/index.d.ts +2 -2
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +33 -2
- package/lib/index.js.map +1 -1
- package/lib/notify.d.ts +1 -1
- package/lib/notify.d.ts.map +1 -1
- package/lib/notify.js +10 -6
- package/lib/notify.js.map +1 -1
- package/lib/types/client/card-controller.d.ts.map +1 -1
- package/lib/types/client/locales.d.ts +48 -2
- package/lib/types/client/locales.d.ts.map +1 -1
- package/lib/types/client/settings-form.d.ts.map +1 -1
- package/lib/types/config.d.ts +46 -0
- package/lib/types/config.d.ts.map +1 -1
- package/package.json +6 -2
package/README.en.md
CHANGED
|
@@ -4,82 +4,206 @@
|
|
|
4
4
|
[](https://www.npmjs.com/package/dsh-messager)
|
|
5
5
|
[](package.json)
|
|
6
6
|
|
|
7
|
-
> **Task-status notification plugin for DeepSeek Harness (DSH).** Get notified via **system notifications** (OS toast), **browser notifications**, and
|
|
7
|
+
> **Task-status notification plugin for DeepSeek Harness (DSH).** Get notified via **system notifications** (OS toast), **browser notifications**, and **Feishu / WeCom / Discord / DingTalk / Telegram** whenever a session needs attention, a task completes, or a task errors — no more staring at the status dots in the session list.
|
|
8
8
|
|
|
9
|
-
A single-package, dual-runtime design: the **host side** (Node) handles system notifications and
|
|
9
|
+
A single-package, dual-runtime design: the **host side** (Node server) handles system notifications and all third-party channels; the **client side** (browser) handles Web Notification. Both share one config source (settings namespace `messager`), editable live from the "Messenger" settings section — config flows through the plugin's own webserver route (`/dsh-messager/config`), so it works on all environments including release/npx/npm installs (no DSH settings allowlist dependency).
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Feature overview
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
-
|
|
16
|
-
-
|
|
17
|
-
|
|
18
|
-
- 🔌 Extensible channels via the `NotifyChannel` interface
|
|
13
|
+
| Need | Implementation |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| Triggers | Needs-interaction (approval `approval/asked`, question/plan-review `ask_user_question`, client `pendingInteraction`), task completed (`agent/status` running→idle, root sessions only + `turn/end` reason), task errored (`agent/error`) |
|
|
16
|
+
| Channels | System (node-notifier toast), browser (Notification API), Feishu (interactive card + HMAC-SHA256 signature), WeCom (markdown + optional signing), Discord (embed card), DingTalk (actionCard + optional signing), Telegram (Bot API HTML message); extensible via the `NotifyChannel` interface |
|
|
17
|
+
| Configurable | Trigger toggles, per-channel enable/verbosity/icon, dedup cooldowns, title prefix, etc. — see [Configuration](#configuration) |
|
|
19
18
|
|
|
20
|
-
Trigger semantics align with the Web UI status dots: orange = needs interaction, green = completed (
|
|
19
|
+
Trigger semantics align with the Web UI status dots: **orange = needs interaction** (`pendingInteraction`), **green = task completed** (`running→idle` and not the current session), **blue = running** (not notified).
|
|
21
20
|
|
|
22
21
|
## Installation
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
> 📖 A full step-by-step guide for **end users** is at [doc/用户安装指南.md](doc/用户安装指南.md) (Chinese) — covering **from source** (clone + local build) and **pnpm** (local checkout / tarball / git / npm) installs.
|
|
24
|
+
|
|
25
|
+
**One step for a formal install** (host + browser client both take effect; then just start with `dsh web` — **no** `--patch` needed):
|
|
25
26
|
|
|
26
27
|
```sh
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
# Build inside the plugin repo
|
|
29
|
+
pnpm install
|
|
30
|
+
pnpm build
|
|
31
|
+
|
|
32
|
+
dsh plugin --profile web add <plugin-path>
|
|
33
|
+
dsh web # or dsh --profile web
|
|
29
34
|
```
|
|
30
35
|
|
|
31
|
-
|
|
36
|
+
> `pnpm install` and `pnpm build` run inside your plugin checkout; replace `<plugin-path>` with that directory (absolute or relative both work).
|
|
32
37
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
```
|
|
38
|
+
> - `--patch` is **not** an install step — it is an optional **development** tool (see "Local development" below): it loads only the host side, writes nothing to the profile, and applies to the current launch only. After installing the bundle, **do not** start the same plugin with `--patch` simultaneously (the host side loads twice and the settings namespace registration conflicts).
|
|
39
|
+
> - When running DSH **from source** (from the deepseek-harness repo root), replace `dsh` with `pnpm dsh` — identical behavior: `pnpm dsh plugin --profile web add …`, `pnpm dsh web`. The profile directory stays `$DSH_HOME/profiles/web` (`dsh web` is the `--profile web` alias).
|
|
40
|
+
> - Installing from git with pnpm ≥ 10 requires approving build scripts: add the package key pnpm prompts for to the profile's `pnpm-workspace.yaml` `allowBuilds` (see the official DSH publish tutorial).
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
### Settings section "Messenger" (available everywhere)
|
|
39
43
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
After installation, a **「通知&信使」** (Messenger) section appears in the DSH settings sidebar (below "Agent presets"; its position is computed dynamically from existing sections, not hard-coded). It hosts the full config form, read/written through the plugin's own webserver route (`/dsh-messager/config`, same-origin check + redacted view) straight to the host `settings` service — **no dependency on the DSH settings allowlist; works out of the box on release (npx) installs**, no patches needed.
|
|
45
|
+
|
|
46
|
+
> The config is the same source as `settings.yaml` (same namespace): any change applies instantly everywhere.
|
|
47
|
+
|
|
48
|
+
## Local development
|
|
49
|
+
|
|
50
|
+
- **Host side (quick)**: from the DSH repo root run
|
|
51
|
+
`pnpm dsh web --patch <plugin-path>/cordis.yml` — loads the TS source directly (HMR works). In source mode the host runs via tsx, so no build is needed to load that path.
|
|
52
|
+
- **Full dual-runtime**: the browser (client) side requires the plugin to enter the Loader as a package for clientModules to scan it into the Web bundle (`--patch` file-path entries are not scanned), so do a full install into the profile:
|
|
53
|
+
```sh
|
|
54
|
+
dsh plugin --profile web add <plugin-path> # source mode: pnpm dsh plugin ...
|
|
55
|
+
pnpm dsh web # run from the DSH source repo
|
|
56
|
+
```
|
|
57
|
+
**Restart** `pnpm dsh web` after `plugin add` (clientModules scans at startup; a running instance does not hot-add new bundles). After changing client code, re-run `pnpm run build:client` in your own repo and refresh the page (the bundle carries a rev hash so it is re-fetched; the DSH repo's `dev:web` watcher only watches in-workspace client plugins, not external ones).
|
|
58
|
+
|
|
59
|
+
Browser notifications require user permission: the plugin requests it once on load when the permission is `default`; if denied, the browser channel degrades silently (other channels are unaffected) — re-authorize in the browser's site settings.
|
|
48
60
|
|
|
49
61
|
## Configuration
|
|
50
62
|
|
|
51
|
-
Precedence: **schema defaults → base (that row's `config:`) → user layer (settings section
|
|
63
|
+
Precedence: **schema defaults → base (that row's `config:`) → user layer (Web settings section)**. The host registers the Loader config as the `messager` namespace's base, therefore:
|
|
64
|
+
|
|
65
|
+
- How to write base differs by usage: for dev debugging write `config:` on the row in `cordis.yml` (patch overlay); for formal installs override the row by `id: messager` in the **profile's `cordis.patch.yml`**, or edit the `cordis.patch.yml` inside the bundle package;
|
|
66
|
+
- The user layer has three entry points, **same source, no conflicts, any change applies instantly** (host `watch` rebuilds channels; client refetches on `settings/document-updated`):
|
|
67
|
+
1. **Settings section**: Settings → "Messenger" (full field form, available in all environments);
|
|
68
|
+
2. **Settings document**: edit the `messager:` block of `$DSH_HOME/settings.yaml` directly (all fields, including dedup throttling not shown in the form);
|
|
69
|
+
3. **RPC**: settings.describe / settings.mutate (host side; the Web allowlist does not affect this plugin's section, since the section uses the plugin's own config route).
|
|
70
|
+
|
|
71
|
+
> Config read/write path: settings section → `GET/POST /dsh-messager/config` (webserver route, same-origin check) → host `settings` service (describe redacted view / mutate per-field ops) → settings.yaml. After a write, the `settings/document-updated` event (built-in DSH forwarding) drives the frontend refresh.
|
|
72
|
+
>
|
|
73
|
+
> 🌐 **i18n**: the section menu and form follow the DSH display language (简体中文 / English); dictionaries are registered in `ctx.locale` (zh/en key sets identical; missing keys fail loud by showing the key name).
|
|
52
74
|
|
|
53
75
|
| Field | Type | Default | Description |
|
|
54
76
|
| --- | --- | --- | --- |
|
|
55
|
-
| `triggers.interaction` | boolean | `true` | Notify
|
|
56
|
-
| `triggers.completed` | boolean | `true` | Notify
|
|
57
|
-
| `triggers.error` | boolean | `true` | Notify
|
|
77
|
+
| `triggers.interaction` | boolean | `true` | Notify when interaction is needed (approval/question/plan-review) |
|
|
78
|
+
| `triggers.completed` | boolean | `true` | Notify when a task completes |
|
|
79
|
+
| `triggers.error` | boolean | `true` | Notify when a task errors |
|
|
58
80
|
| `system.enabled` | boolean | `true` | System notification channel |
|
|
59
|
-
| `system.icon` | string | - | Absolute icon path (must exist) |
|
|
81
|
+
| `system.icon` | string | - | Absolute icon path (node-notifier needs a file path, **and the file must exist**) |
|
|
60
82
|
| `system.verbosity` | `minimal\|normal\|detailed` | `normal` | System content verbosity |
|
|
61
83
|
| `browser.enabled` | boolean | `true` | Browser notification channel |
|
|
62
|
-
| `browser.
|
|
84
|
+
| `browser.icon` | string | - | Icon URL or data URL |
|
|
85
|
+
| `browser.onlyWhenHidden` | boolean | `true` | Notify only when the page is hidden/unfocused |
|
|
63
86
|
| `browser.verbosity` | `minimal\|normal\|detailed` | `normal` | Browser content verbosity |
|
|
64
87
|
| `feishu.enabled` | boolean | `false` | Feishu bot (webhook) channel |
|
|
65
88
|
| `feishu.webhookUrl` | string | - | Custom bot webhook URL |
|
|
66
|
-
| `feishu.secret` | string (secret) | - | Signing secret |
|
|
89
|
+
| `feishu.secret` | string (secret) | - | Signing secret (bot "security settings - signature") |
|
|
67
90
|
| `feishu.timeoutMs` | number | `5000` | Per-request timeout |
|
|
68
91
|
| `feishu.verbosity` | `minimal\|normal\|detailed` | `normal` | Card content verbosity |
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
| `
|
|
73
|
-
| `
|
|
74
|
-
| `
|
|
92
|
+
| `wecom.enabled` | boolean | `false` | WeCom group bot (webhook) channel |
|
|
93
|
+
| `wecom.webhookUrl` | string | - | Group bot webhook URL (with `?key=`) |
|
|
94
|
+
| `wecom.secret` | string (secret) | - | Signing secret ("security settings - signing", HMAC-SHA256, no URL encoding) |
|
|
95
|
+
| `wecom.timeoutMs` | number | `5000` | Per-request timeout |
|
|
96
|
+
| `wecom.verbosity` | `minimal\|normal\|detailed` | `normal` | Message content verbosity |
|
|
97
|
+
| `discord.enabled` | boolean | `false` | Discord channel (webhook) |
|
|
98
|
+
| `discord.webhookUrl` | string | - | Discord webhook URL (`.../api/webhooks/<id>/<token>`) |
|
|
99
|
+
| `discord.timeoutMs` | number | `5000` | Per-request timeout |
|
|
100
|
+
| `discord.verbosity` | `minimal\|normal\|detailed` | `normal` | Embed content verbosity |
|
|
101
|
+
| `dingtalk.enabled` | boolean | `false` | DingTalk custom bot (webhook) channel |
|
|
102
|
+
| `dingtalk.webhookUrl` | string | - | Custom bot webhook URL (with `?access_token=`) |
|
|
103
|
+
| `dingtalk.secret` | string (secret) | - | Signing secret ("security settings - signing", HMAC-SHA256 + URL encoding) |
|
|
104
|
+
| `dingtalk.timeoutMs` | number | `5000` | Per-request timeout |
|
|
105
|
+
| `dingtalk.verbosity` | `minimal\|normal\|detailed` | `normal` | Card content verbosity |
|
|
106
|
+
| `telegram.enabled` | boolean | `false` | Telegram channel (Bot API) |
|
|
107
|
+
| `telegram.botToken` | string (secret) | - | Bot token (from @BotFather) |
|
|
108
|
+
| `telegram.chatId` | string | - | Target chat_id (numeric ID or `@channel-username`) |
|
|
109
|
+
| `telegram.timeoutMs` | number | `5000` | Per-request timeout |
|
|
110
|
+
| `telegram.verbosity` | `minimal\|normal\|detailed` | `normal` | Message content verbosity |
|
|
111
|
+
| `dedup.interactionCooldownMs` | number | `10000` | Cooldown for same session/trigger (also the cross-tab dedup window) |
|
|
112
|
+
| `dedup.completedDebounceMs` | number | `1000` | Completion debounce (waits for the turn/end reason, merges boundary jitter) |
|
|
113
|
+
| `dedup.perChannelPerMinute` | number | `20` | Per-channel per-minute cap (protects against third-party rate limits/spam) |
|
|
114
|
+
| `message.titlePrefix` | string | - | Title prefix, e.g. `[DSH]` |
|
|
115
|
+
| `message.includeSessionTitle` | boolean | `true` | Include session title in the body |
|
|
116
|
+
| `message.guiUrl` | string | `http://127.0.0.1:3080` | "Open" link/button target |
|
|
117
|
+
|
|
118
|
+
Verbosity: `minimal` = title only; `normal` adds session title/tool name/end reason/error summary; `detailed` adds turn/step, approval reason and the GUI link.
|
|
119
|
+
|
|
120
|
+
## Trigger signals (event → notification mapping)
|
|
121
|
+
|
|
122
|
+
| Trigger | Host side (system/feishu/wecom/discord/dingtalk/telegram) | Client side (browser) |
|
|
123
|
+
| --- | --- | --- |
|
|
124
|
+
| Approval | `session/event` `approval/asked` | summary `pendingInteraction==='approval'` appears |
|
|
125
|
+
| Question / plan-review | `session/event` `tool/call` (`ask_user_question`) | `pendingInteraction==='question'/'plan-review'` appears |
|
|
126
|
+
| Task completed | `agent/status` running→idle (root sessions only) + `turn/end` reason | summary `running:true→false` and not the current session |
|
|
127
|
+
| Task errored | `agent/error` | - (covered by the host side) |
|
|
128
|
+
|
|
129
|
+
## Channel extension
|
|
130
|
+
|
|
131
|
+
Add a third-party channel (DingTalk / WeCom / Telegram…) by implementing the `NotifyChannel` interface and registering it in `buildChannels()` in `src/index.ts`:
|
|
132
|
+
|
|
133
|
+
```ts
|
|
134
|
+
export interface NotifyChannel {
|
|
135
|
+
readonly id: string
|
|
136
|
+
send(payload: NotificationPayload): Promise<void>
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## Project structure
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
dsh-messager/
|
|
144
|
+
├── package.json # dsh.bundle + dsh.client dual declarations; exports["./client"]
|
|
145
|
+
├── tsconfig.json # host side (Node)
|
|
146
|
+
├── tsconfig.client.json # client declaration output (lib/types/client)
|
|
147
|
+
├── tsdown.config.ts # client bundle (__ModuleLoader__.load contract)
|
|
148
|
+
├── cordis.yml # local development overlay (host side)
|
|
149
|
+
├── cordis.patch.yml # bundle config layer (applies after install)
|
|
150
|
+
├── assets/icon.png # default notification icon
|
|
151
|
+
├── src/
|
|
152
|
+
│ ├── index.ts # host apply: event wiring + settings registration + channel building + route mounting
|
|
153
|
+
│ ├── config.ts # Config schema (shared by Loader config and settings)
|
|
154
|
+
│ ├── config-shared.ts # cross-end shared types for the config route (host/client)
|
|
155
|
+
│ ├── config-route.ts # webserver config route (GET view / POST ops, same-origin check)
|
|
156
|
+
│ ├── signals.ts # event → Signal extraction (pure functions)
|
|
157
|
+
│ ├── notify.ts # dispatch: filter/cooldown/debounce/rate-limit + NotifyChannel interface
|
|
158
|
+
│ ├── templates.ts # verbosity template rendering (pure functions)
|
|
159
|
+
│ ├── settings.ts # settings namespace registration (base = Loader config)
|
|
160
|
+
│ ├── channels/ # system (node-notifier), feishu/wecom/discord/dingtalk/telegram (webhook/Bot API + signing)
|
|
161
|
+
│ └── client/ # browser side: sessions diff, Notification, settings section, config sync
|
|
162
|
+
│ ├── index.ts # section registration (dynamic order) + browser notifications + config route accessor
|
|
163
|
+
│ ├── section.tsx # "Messenger" settings section component
|
|
164
|
+
│ ├── settings-form.tsx # shared form body (groups + FieldRow + action bar)
|
|
165
|
+
│ ├── card-controller.ts # form controller (pure logic, unit-testable)
|
|
166
|
+
│ ├── fetch-scope.ts # fetch adapter for ScopeLike (config route)
|
|
167
|
+
│ ├── locales.ts # zh/en dictionaries (ctx.locale registration)
|
|
168
|
+
│ ├── config.ts # browser-notification config handle (via the config route)
|
|
169
|
+
│ └── diff.ts # session summary diff (pure functions)
|
|
170
|
+
└── tests/ # vitest unit tests (126)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Testing
|
|
174
|
+
|
|
175
|
+
```sh
|
|
176
|
+
pnpm test # 126 unit tests: signal extraction/templates/dispatch/channel payloads & signatures/config parsing/client diff/config route/fetch scope/locale consistency/form gating
|
|
177
|
+
pnpm typecheck # host side
|
|
178
|
+
pnpm build # host tsc + client declarations + client bundle (lib/)
|
|
179
|
+
```
|
|
75
180
|
|
|
76
181
|
## Known limitations
|
|
77
182
|
|
|
78
|
-
- Browser notifications require site permission; with `onlyWhenHidden=false` they also pop
|
|
79
|
-
- Multi-tab dedup uses localStorage cooldown;
|
|
80
|
-
- Subagent completions are not notified (root sessions only)
|
|
81
|
-
- Channel failures are only logged; they don't affect other channels or the plugin.
|
|
82
|
-
- Completion/interaction dedup state is in-memory and resets
|
|
183
|
+
- Browser notifications require site permission; with `onlyWhenHidden=false` they also pop while visible.
|
|
184
|
+
- Multi-tab dedup uses a localStorage cooldown; different browsers notify independently.
|
|
185
|
+
- Subagent completions are not notified (root sessions only), to avoid noise.
|
|
186
|
+
- Channel failures (webhook timeouts, unavailable toasts) are only logged; they don't affect other channels or the plugin.
|
|
187
|
+
- Completion/interaction dedup state is in-memory and resets on DSH restart (acceptable).
|
|
188
|
+
|
|
189
|
+
### System notification (node-notifier) cross-platform prerequisites
|
|
190
|
+
|
|
191
|
+
`node-notifier` invokes completely different underlying programs on the three platforms:
|
|
192
|
+
|
|
193
|
+
| Platform | Backend | Prerequisites / differences |
|
|
194
|
+
| --- | --- | --- |
|
|
195
|
+
| Windows | PowerShell ToastNotification | Built in, nothing to install; `sound` maps reliably only on Windows |
|
|
196
|
+
| macOS | terminal-notifier | First use **downloads** a third-party binary, and a logged-in GUI session (Dock present) is required; `sound` has no effect |
|
|
197
|
+
| Linux | notify-send (libnotify) | Requires `libnotify-bin` and a **running notification daemon** (GNOME Shell / Plasma / mako / dunst etc.); `sound` has no effect |
|
|
198
|
+
|
|
199
|
+
- **Icon**: `system.icon` must be an **existing file path**. Windows usually degrades silently on a missing path, but Linux/macOS may fail outright — the channel layer validates existence and falls back to no icon.
|
|
200
|
+
- **Environment differences are not plugin bugs**: on Linux without a notification daemon, or macOS unable to download terminal-notifier / not in a GUI session, notifications may not pop or fail silently — check those prerequisites first, not the plugin; on failure the dispatcher logs the concrete error via `logWarn`.
|
|
201
|
+
|
|
202
|
+
## Roadmap
|
|
203
|
+
|
|
204
|
+
- Third-party channel extension: email
|
|
205
|
+
- Trigger extension: background job completion, goal round completion
|
|
206
|
+
- Notification history, per-session mute, do-not-disturb windows
|
|
83
207
|
|
|
84
208
|
## License
|
|
85
209
|
|