dsh-messager 0.2.0 → 0.3.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 CHANGED
@@ -24,14 +24,14 @@ Trigger semantics align with the Web UI status dots: **orange = needs interactio
24
24
 
25
25
  **One step for a formal install** (host + browser client both take effect; then just start with `dsh web` — **no** `--patch` needed):
26
26
 
27
- ```sh
27
+ ``sh
28
28
  # Build inside the plugin repo
29
29
  pnpm install
30
30
  pnpm build
31
31
 
32
32
  dsh plugin --profile web add <plugin-path>
33
33
  dsh web # or dsh --profile web
34
- ```
34
+ ``
35
35
 
36
36
  > `pnpm install` and `pnpm build` run inside your plugin checkout; replace `<plugin-path>` with that directory (absolute or relative both work).
37
37
 
@@ -50,10 +50,10 @@ After installation, a **「通知&信使」** (Messenger) section appears in the
50
50
  - **Host side (quick)**: from the DSH repo root run
51
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
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
53
+ ``sh
54
54
  dsh plugin --profile web add <plugin-path> # source mode: pnpm dsh plugin ...
55
55
  pnpm dsh web # run from the DSH source repo
56
- ```
56
+ ``
57
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
58
 
59
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.
@@ -130,16 +130,16 @@ Verbosity: `minimal` = title only; `normal` adds session title/tool name/end rea
130
130
 
131
131
  Add a third-party channel (DingTalk / WeCom / Telegram…) by implementing the `NotifyChannel` interface and registering it in `buildChannels()` in `src/index.ts`:
132
132
 
133
- ```ts
133
+ ``ts
134
134
  export interface NotifyChannel {
135
135
  readonly id: string
136
136
  send(payload: NotificationPayload): Promise<void>
137
137
  }
138
- ```
138
+ ``
139
139
 
140
140
  ## Project structure
141
141
 
142
- ```
142
+ ``
143
143
  dsh-messager/
144
144
  ├── package.json # dsh.bundle + dsh.client dual declarations; exports["./client"]
145
145
  ├── tsconfig.json # host side (Node)
@@ -168,16 +168,23 @@ dsh-messager/
168
168
  │ ├── config.ts # browser-notification config handle (via the config route)
169
169
  │ └── diff.ts # session summary diff (pure functions)
170
170
  └── tests/ # vitest unit tests (126)
171
- ```
171
+ ``
172
172
 
173
173
  ## Testing
174
174
 
175
- ```sh
175
+ ``sh
176
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
177
  pnpm typecheck # host side
178
178
  pnpm build # host tsc + client declarations + client bundle (lib/)
179
- ```
180
-
179
+ ``
180
+
181
+ ## Version compatibility (dsh-messager 0.3.0 / DSH 0.1.2-alpha.5)
182
+
183
+ - **v0.3.0 supports only DSH `0.1.2-alpha.5`**; all `@deepseek-ai/dsh-*` peerDependencies are pinned to that version.
184
+ - Compatibility notes:
185
+ - The removed `dsh-client-runtime` is no longer used. Client session lists use `dsh-api-session-controller`, interaction state uses `dsh-client-ui-session`, and slot services use `dsh-client-ui-renderer`.
186
+ - The `messager` settings namespace is registered from the Loader config and can also be read/written through the plugin's own `/dsh-messager/config` route, which is not subject to DSH's settings namespace allowlist.
187
+
181
188
  ## Known limitations
182
189
 
183
190
  - Browser notifications require site permission; with `onlyWhenHidden=false` they also pop while visible.
package/README.md CHANGED
@@ -13,11 +13,11 @@ DeepSeek Harness(DSH)**任务状态通知插件**:会话需要交互、任
13
13
 
14
14
  | 需求 | 实现 |
15
15
  | --- | --- |
16
- | 触发时机 | 需要交互(审批 `approval/asked`、提问/计划待审 `ask_user_question`、客户端 pendingInteraction)、任务完成(`agent/status` running→idle 且仅根会话 + `turn/end` 原因)、任务出错(`agent/error`) |
16
+ | 触发时机 | 需要交互(审批 `approval/asked`、提问/计划待审 `ask_user_question`、客户端 `uiSession.pendingInteractions`)、任务完成(`agent/status` running→idle 且仅根会话 + `turn/end` 原因)、任务出错(`agent/error`) |
17
17
  | 推送路径 | 系统通知(node-notifier toast)、浏览器通知(Notification API)、飞书(interactive 卡片 + HMAC-SHA256 签名)、企业微信(markdown + 可选加签)、Discord(embed 卡片)、钉钉(actionCard + 可选加签)、Telegram(Bot API HTML 消息);`NotifyChannel` 接口可扩展 |
18
18
  | 可配置 | 触发开关、各通道启停/verbosity/icon、去重冷却、标题前缀等,见[配置](#配置) |
19
19
 
20
- 触发语义与 Web UI 状态圆点完全对齐:**橙点 = 需要交互**(`pendingInteraction`),**绿点 = 任务完成**
20
+ 触发语义与 Web UI 状态圆点完全对齐:**橙点 = 需要交互**(`uiSession.pendingInteractions`),**绿点 = 任务完成**
21
21
  (`running→idle` 且非当前会话),**蓝点 = 运行中**(不通知)。
22
22
 
23
23
  ## 安装
@@ -151,8 +151,8 @@ host 端把 Loader config 注册为 settings 命名空间 `messager` 的 base
151
151
 
152
152
  | 触发 | host 端(system/feishu/wecom/discord/dingtalk/telegram) | client 端(browser) |
153
153
  | --- | --- | --- |
154
- | 审批 | `session/event` `approval/asked` | 摘要 `pendingInteraction==='approval'` 出现 |
155
- | 提问/计划待审 | `session/event` `tool/call`(`ask_user_question`) | `pendingInteraction==='question'/'plan-review'` 出现 |
154
+ | 审批 | `session/event` `approval/asked` | `ctx.uiSession.pendingInteractions` `kind==='approval'` 从无到有 |
155
+ | 提问/计划待审 | `session/event` `tool/call`(`ask_user_question`) | `ctx.uiSession.pendingInteractions` 中 `kind==='question'/'plan-review'` 从无到有 |
156
156
  | 任务完成 | `agent/status` running→idle(仅根会话)+`turn/end` 原因 | 摘要 `running:true→false` 且非当前会话 |
157
157
  | 任务出错 | `agent/error` | -(host 端覆盖) |
158
158
 
@@ -179,6 +179,7 @@ dsh-messager/
179
179
  ├── cordis.yml # 本地开发覆盖层(host 端)
180
180
  ├── cordis.patch.yml # 分发包配置层(安装后生效)
181
181
  ├── assets/icon.png # 默认通知图标
182
+ ├── doc/plan/ # 规划存档(01 起编号)
182
183
  ├── src/
183
184
  │ ├── index.ts # host apply:事件接线 + settings 注册 + 通道构建 + 路由挂载
184
185
  │ ├── config.ts # Config schema(Loader config 与 settings 共用)
@@ -197,18 +198,30 @@ dsh-messager/
197
198
  │ ├── fetch-scope.ts # ScopeLike 的 fetch 适配层(配置路由)
198
199
  │ ├── locales.ts # zh/en 字典(ctx.locale 注册)
199
200
  │ ├── config.ts # 浏览器通知的配置句柄(走配置路由)
200
- │ └── diff.ts # 会话摘要 diff(纯函数)
201
- └── tests/ # vitest 单元测试(126 个)
201
+ │ └── diff.ts # 完成摘要 / 待交互状态 diff(纯函数)
202
+ └── tests/ # vitest 单元测试(129 个)
202
203
  ```
203
204
 
204
205
  ## 测试
205
206
 
206
207
  ```sh
207
- pnpm test # 126 个单元测试:信号提取/模板/调度/各通道签名与载荷/配置解析/client diff/配置路由/fetch scope/字典一致性/表单门控
208
+ pnpm test # 129 个单元测试:信号提取/模板/调度/各通道签名与载荷/配置解析/client diff/配置路由/fetch scope/字典一致性/表单门控
208
209
  pnpm typecheck # host 端
209
210
  pnpm build # host tsc + client 声明 + client bundle(lib/)
210
211
  ```
211
212
 
213
+ ## 版本兼容(dsh-messager 0.3.0 / DSH 0.1.2-alpha.5)
214
+
215
+ - **v0.3.0 仅支持 DSH `0.1.2-alpha.5`**;所有 `@deepseek-ai/dsh-*` peerDependencies
216
+ 统一锁定该版本,不再兼容旧 RC 接口。
217
+ - client 端适配新版拆分:会话列表来自 `dsh-api-session-controller`,交互状态来自
218
+ `dsh-client-ui-session` 的 `ctx.uiSession.pendingInteractions`,`ctx.slots` 由
219
+ `dsh-client-ui-renderer` 提供;不再依赖已移除的 `dsh-client-runtime`。
220
+ - 完成通知仍按会话摘要 `running: true → false` 且非当前会话触发;交互通知仅在
221
+ `approval` / `question` / `plan-review` 从无到有时触发,首次订阅只建立基线。
222
+ - host 设置使用字符串命名空间 `messager`;配置读写继续走插件自有 webServer 路由
223
+ `/dsh-messager/config`,现有 schema、settings 数据和第三方通道配置无需迁移或重置。
224
+
212
225
  ## 已知边界
213
226
 
214
227
  - 浏览器通知需站点权限;`onlyWhenHidden=false` 时页面可见也会弹。
@@ -238,3 +251,5 @@ pnpm build # host tsc + client 声明 + client bundle(lib/)
238
251
  - 第三方通道扩展:邮件
239
252
  - 触发扩展:后台 job 完成、goal 轮次完成
240
253
  - 通知历史、按会话静音、勿扰时段
254
+
255
+ 规划存档见 `doc/plan/01-通知插件实施规划.md`。
package/lib/client.js CHANGED
@@ -5,7 +5,7 @@ window.__ModuleLoader__.load({
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  let react_jsx_runtime = require("react/jsx-runtime");
8
- //#region node_modules/.pnpm/@deepseek-ai+cosmokit@1.8.2/node_modules/@deepseek-ai/cosmokit/lib/index.js
8
+ //#region node_modules/.pnpm/@deepseek-ai+cosmokit@1.8.3/node_modules/@deepseek-ai/cosmokit/lib/index.js
9
9
  /** Return true when a value is `null` or `undefined`. */
10
10
  function isNullable(value) {
11
11
  return value === null || value === void 0;
@@ -201,7 +201,7 @@ window.__ModuleLoader__.load({
201
201
  Time.template = template;
202
202
  })(Time || (Time = {}));
203
203
  //#endregion
204
- //#region node_modules/.pnpm/@deepseek-ai+schemastery@3.18.1/node_modules/@deepseek-ai/schemastery/lib/index.mjs
204
+ //#region node_modules/.pnpm/@deepseek-ai+schemastery@3.18.2/node_modules/@deepseek-ai/schemastery/lib/index.mjs
205
205
  const kSchema = Symbol.for("schemastery");
206
206
  const kValidationError = Symbol.for("ValidationError");
207
207
  globalThis.__schemastery_index__ ??= 0;
@@ -935,12 +935,6 @@ window.__ModuleLoader__.load({
935
935
  if (summary === void 0) continue;
936
936
  const before = previous[id];
937
937
  if (before === void 0) continue;
938
- if (summary.pendingInteraction !== void 0 && before.pendingInteraction === void 0) notices.push({
939
- kind: "interaction",
940
- sessionId: id,
941
- interaction: summary.pendingInteraction,
942
- title: summary.displayTitle
943
- });
944
938
  if (before.running && !summary.running && id !== current) notices.push({
945
939
  kind: "completed",
946
940
  sessionId: id,
@@ -949,6 +943,35 @@ window.__ModuleLoader__.load({
949
943
  }
950
944
  return notices;
951
945
  }
946
+ /** 仅把 DSH Web UI 有专用展示语义的待交互 kind 映射为通知类型。 */
947
+ function clientInteractionKindOf(kind) {
948
+ switch (kind) {
949
+ case "approval":
950
+ case "plan-review":
951
+ case "question": return kind;
952
+ default: return;
953
+ }
954
+ }
955
+ /**
956
+ * 对比两次 uiSession 待交互快照。
957
+ * 仅从无到有时通知;首次订阅由调用方把当前快照设为 previous,因此不会补发历史通知。
958
+ */
959
+ function diffPendingInteractions(previous, next, summaries) {
960
+ const notices = [];
961
+ for (const [sessionId, pending] of next) {
962
+ if (previous.has(sessionId)) continue;
963
+ const interaction = clientInteractionKindOf(pending.kind);
964
+ if (interaction === void 0) continue;
965
+ const title = summaries[sessionId]?.displayTitle;
966
+ notices.push({
967
+ kind: "interaction",
968
+ sessionId,
969
+ interaction,
970
+ ...title === void 0 ? {} : { title }
971
+ });
972
+ }
973
+ return notices;
974
+ }
952
975
  //#endregion
953
976
  //#region src/client/card-controller.ts
954
977
  /** 字段键:`group.field`。 */
@@ -2182,6 +2205,7 @@ window.__ModuleLoader__.load({
2182
2205
  /** 依赖的客户端服务:会话列表、远程事件(document-updated)、槽位、locale。 */
2183
2206
  const inject = [
2184
2207
  "sessions",
2208
+ "uiSession",
2185
2209
  "remote",
2186
2210
  "slots",
2187
2211
  "locale"
@@ -2201,12 +2225,18 @@ window.__ModuleLoader__.load({
2201
2225
  }
2202
2226
  /** 列表快照变化 → 通知。 */
2203
2227
  onListChange(previous, next) {
2228
+ this.onNotices(diffSessionSummaries(previous.byId, next.byId, next.current));
2229
+ }
2230
+ /** 待交互快照变化 → 通知。 */
2231
+ onPendingChange(previous, next, sessions) {
2232
+ this.onNotices(diffPendingInteractions(previous, next, sessions.byId));
2233
+ }
2234
+ onNotices(notices) {
2204
2235
  const config = this.config.get();
2205
2236
  if (!config.browser.enabled) return;
2206
2237
  if (typeof Notification === "undefined") return;
2207
2238
  if (Notification.permission !== "granted") return;
2208
2239
  if (config.browser.onlyWhenHidden && document.visibilityState !== "hidden") return;
2209
- const notices = diffSessionSummaries(previous.byId, next.byId, next.current);
2210
2240
  for (const notice of notices) {
2211
2241
  if (!this.allow(notice, config)) continue;
2212
2242
  this.show(notice, config);
@@ -2299,6 +2329,13 @@ window.__ModuleLoader__.load({
2299
2329
  previous = next;
2300
2330
  });
2301
2331
  ctx.effect(() => () => offList(), "dsh-messager: sessions subscription");
2332
+ let previousPending = ctx.uiSession.pendingInteractions.getSnapshot();
2333
+ const offPending = ctx.uiSession.pendingInteractions.subscribe(() => {
2334
+ const next = ctx.uiSession.pendingInteractions.getSnapshot();
2335
+ notifier.onPendingChange(previousPending, next, ctx.sessions.list.getSnapshot());
2336
+ previousPending = next;
2337
+ });
2338
+ ctx.effect(() => () => offPending(), "dsh-messager: pending interactions subscription");
2302
2339
  const controller = new MessagerCardController(fetchScope.scope, CARD_FIELDS);
2303
2340
  ctx.effect(() => ctx.locale.register(LOCALE_NS, {
2304
2341
  zh,