@telosmaylx/dsh-session-notify 0.1.4 → 0.1.6
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.md +29 -4
- package/lib/client.js +119 -8
- package/lib/index.js +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -274,9 +274,10 @@ Session completed (took 3m25s, used 1,240 in / 3,560 out).
|
|
|
274
274
|
|
|
275
275
|
| 权限状态 | 行为 |
|
|
276
276
|
| --- | --- |
|
|
277
|
-
| `default`(未决定) |
|
|
278
|
-
| `granted` |
|
|
279
|
-
| `denied
|
|
277
|
+
| `default`(未决定) | 完成事件只发 toast;设置面板「通知权限」区提供「请求授权」按钮(**用户手势内请求**——Chromium 会忽略非手势的自动请求,因此插件不再自动请求) |
|
|
278
|
+
| `granted` | 按「推送方式」发系统通知(独立 tag,互不覆盖) |
|
|
279
|
+
| `denied`(被浏览器屏蔽) | 仅 toast;设置面板显示地址栏操作指引(权限图标 → 网站设置 → 通知 → 允许) |
|
|
280
|
+
| `undefined`(非安全上下文 / 不支持) | 仅 toast;建议改用「仅页内提示」 |
|
|
280
281
|
|
|
281
282
|
---
|
|
282
283
|
|
|
@@ -292,10 +293,18 @@ Session completed (took 3m25s, used 1,240 in / 3,560 out).
|
|
|
292
293
|
| --- | --- |
|
|
293
294
|
| 预设 | 下拉选择内置或自定义预设;「新增」把当前配置另存为自定义预设;当前预设可「删除」 |
|
|
294
295
|
| 语言 | 5 种语言单选,切换即时重渲染整个面板 |
|
|
296
|
+
| 推送方式 | 三选一:双通道(系统通知 + 页内提示,默认)/ 仅系统通知 / 仅页内提示 |
|
|
295
297
|
| 推送标题 | `{title}` 引用会话标题,留空则直接用会话标题 |
|
|
296
298
|
| 模板 × 5 | 每条结束原因(完成、出错、中止、阻塞、输出上限)独立一个 Chip 编辑器:文字 + 内联信息胶囊,光标处插入、点击移除、实时预览 |
|
|
297
299
|
| 跳过子代理会话 | 复选框(保存时一并写入设置文档) |
|
|
298
|
-
|
|
|
300
|
+
| 通知权限 | 状态实时显示:已授权(绿)/ 尚未授权(附「请求授权」按钮)/ 已被浏览器屏蔽(附地址栏操作指引)/ 环境不支持 |
|
|
301
|
+
| 保存 | 写入宿主设置文档(`language` / `templates` / `titleTemplate` / `pushMode`);保存后显示「点击刷新」链接 |
|
|
302
|
+
|
|
303
|
+
> [!NOTE]
|
|
304
|
+
> 「推送方式」的取舍:`dual`(默认)同时弹 Windows 系统通知与页内 toast,toast 是保底通道,防止系统通知被平台静默(专注助手、通知横幅关闭)。但 QQ 浏览器等 Chromium 壳浏览器会把 `Notification` 渲染成页内横幅而非 Windows 通知中心——此时 `dual` 会造成页内两个提示。这类浏览器请选「仅页内提示」(不再调用 `Notification`,页内只有一个小 toast)。
|
|
305
|
+
|
|
306
|
+
> [!NOTE]
|
|
307
|
+
> 系统通知(`Notification` API)能否弹出由**浏览器与站点访问方式**共同决定:Edge/Chrome 对"不熟悉"的站点会**自动屏蔽通知**(地址栏出现「通知已屏蔽」)——点击地址栏左侧权限图标 → 网站设置 → 通知 → 允许即可恢复;`http://IP` 这类非安全上下文访问时 `Notification` 根本不存在,请改用「仅页内提示」。设置面板「通知权限」区域会实时显示当前状态并给出对应操作指引(可一键请求授权)。Firefox 窗口聚焦时通知显示为页内横幅、失焦才进系统通知中心。
|
|
299
308
|
|
|
300
309
|
> [!NOTE]
|
|
301
310
|
> 面板中「跳过子代理会话」保存的是设置文档里的布尔值;宿主 `cordis.patch.yml` 的 `config.skipSubagents` 是其启动默认值,两者任一为真即跳过。
|
|
@@ -501,12 +510,28 @@ node scripts/verify-notice.mjs <session.jsonl.zstd>
|
|
|
501
510
|
|
|
502
511
|
</details>
|
|
503
512
|
|
|
513
|
+
<details>
|
|
514
|
+
<summary><b>为什么 Edge 推不了系统通知?QQ 浏览器为什么只有页内横幅?</b></summary>
|
|
515
|
+
|
|
516
|
+
两者都是浏览器行为,插件无法强制:
|
|
517
|
+
|
|
518
|
+
- **Edge / Chrome**:对"不熟悉"的站点会**自动屏蔽通知**(地址栏出现「通知已屏蔽」)。点击地址栏左侧权限图标 → 网站设置 → 通知 → 允许即可恢复,之后正常弹 Windows 通知中心。也可在浏览器通知设置中关闭「自动屏蔽」。
|
|
519
|
+
- **QQ 浏览器等国产 Chromium 壳**:把 `Notification` 固定渲染为页内横幅,不经过 Windows 通知中心,且无系统通知选项——请用「推送方式 = 仅页内提示」,页内只有一个插件自带的小 toast。
|
|
520
|
+
- **Firefox**:窗口聚焦时通知显示为页内横幅,失焦/最小化才进系统通知中心;权限需在地址栏手动允许。
|
|
521
|
+
- 另注意:`http://IP` 访问(非安全上下文)时 `Notification` 不存在,任何浏览器都弹不了系统通知。
|
|
522
|
+
|
|
523
|
+
设置面板「通知权限」区域会实时显示当前状态与对应操作指引。
|
|
524
|
+
|
|
525
|
+
</details>
|
|
526
|
+
|
|
504
527
|
---
|
|
505
528
|
|
|
506
529
|
## 更新日志
|
|
507
530
|
|
|
508
531
|
| 版本 | 日期 | 变更 |
|
|
509
532
|
| --- | --- | --- |
|
|
533
|
+
| **0.1.6** | 2026-08-29 | 设置面板新增「通知权限」状态区(授权状态实时显示 + 一键请求授权按钮 + 被屏蔽时的地址栏操作指引);授权改为**用户手势内请求**(Chromium 忽略非手势自动请求,Edge 对不熟悉站点自动屏蔽通知的典型场景得以解决);FAQ 新增浏览器差异说明 |
|
|
534
|
+
| **0.1.5** | 2026-08-29 | 新增「推送方式」设置(双通道 / 仅系统通知 / 仅页内提示):解决 QQ 浏览器等 Chromium 壳把 `Notification` 渲染成页内横幅导致的双提示;`pushMode` 加入设置 schema 与设置面板 |
|
|
510
535
|
| **0.1.4** | 2026-08-28 | 补充完整卸载支持:`dispose` 生命周期收尾(host 置卸载标志抑制待追加微任务;client 清理正文轮询定时器、`__dsch_notify_debug` 钩子、toast 容器);卸载文档与 FAQ 同步 |
|
|
511
536
|
| **0.1.3** | 2026-08-28 | 声明官方 `dsh.bundle` manifest(`dsh plugin add` 一条命令自动挂载);settings 重试定时器改为 `ctx.effect()` 包装(Cordis effect 纪律);安装文档重排 |
|
|
512
537
|
| 0.1.2 | 2026-08-27 | 包更名至 `@telosmaylx` scope(npm 用户名作用域) |
|
package/lib/client.js
CHANGED
|
@@ -53,7 +53,10 @@ window.__ModuleLoader__.load({
|
|
|
53
53
|
placeholder: '自定义文案:点「+ 插入信息」,或直接输入文字',
|
|
54
54
|
insertInfo: '+ 插入信息', insertTitle: '在光标处插入信息', preview: '预览:',
|
|
55
55
|
skipSubagents: '跳过子代理会话',
|
|
56
|
+
pushMode: '推送方式', pushDual: '系统通知 + 页内提示', pushSystem: '仅系统通知', pushToast: '仅页内提示',
|
|
56
57
|
discard: '放弃', save: '保存', saving: '保存中…', saved: '已保存 ✓ · 刷新页面后生效', saveFailed: '保存失败:', refresh: '点击刷新',
|
|
58
|
+
permTitle: '通知权限', permGranted: '已授权:系统通知可用', permDefault: '尚未授权', permDenied: '已被浏览器屏蔽', permUnsupported: '当前环境不支持系统通知',
|
|
59
|
+
permDefaultHint: '点击「请求授权」;若浏览器未弹出询问,请用地址栏权限图标手动允许', permDeniedHint: '浏览器阻止了本网站的通知:点击地址栏左侧权限图标 → 网站设置 → 通知 → 允许,然后刷新页面', permUnsupportedHint: '系统通知需要 HTTPS 或 localhost 访问;可改用「仅页内提示」', permRequest: '请求授权',
|
|
57
60
|
saveAs: '新增', update: '修改', remove: '删除', presetNamePlaceholder: '预设名称', confirm: '确定', cancel: '取消', customSaved: '自定义预设已保存', customUpdated: '自定义预设已更新', customDeleted: '自定义预设已删除', saveAsTitle: '将当前配置另存为自定义预设', updateTitle: '更新当前自定义预设', removeTitle: '删除当前自定义预设', fromPreset: '来自:', modifiedSuffix: ' · 已修改',
|
|
58
61
|
presetNameRequired: '请输入预设名称',
|
|
59
62
|
loading: '加载中…', unavailable: '设置不可用(宿主未注册命名空间)',
|
|
@@ -70,7 +73,10 @@ window.__ModuleLoader__.load({
|
|
|
70
73
|
placeholder: '自訂文案:點「+ 插入資訊」,或直接輸入文字',
|
|
71
74
|
insertInfo: '+ 插入資訊', insertTitle: '在游標處插入資訊', preview: '預覽:',
|
|
72
75
|
skipSubagents: '跳過子代理會話',
|
|
76
|
+
pushMode: '推送方式', pushDual: '系統通知 + 頁內提示', pushSystem: '僅系統通知', pushToast: '僅頁內提示',
|
|
73
77
|
discard: '放棄', save: '儲存', saving: '儲存中…', saved: '已儲存 ✓ · 重新整理頁面後生效', saveFailed: '儲存失敗:', refresh: '點擊重新整理',
|
|
78
|
+
permTitle: '通知權限', permGranted: '已授權:系統通知可用', permDefault: '尚未授權', permDenied: '已被瀏覽器封鎖', permUnsupported: '目前環境不支援系統通知',
|
|
79
|
+
permDefaultHint: '點擊「請求授權」;若瀏覽器未彈出詢問,請用網址列權限圖示手動允許', permDeniedHint: '瀏覽器封鎖了本網站的通知:點擊網址列左側權限圖示 → 網站設定 → 通知 → 允許,然後重新整理頁面', permUnsupportedHint: '系統通知需要 HTTPS 或 localhost 存取;可改用「僅頁內提示」', permRequest: '請求授權',
|
|
74
80
|
saveAs: '新增', update: '修改', remove: '刪除', presetNamePlaceholder: '預設名稱', confirm: '確定', cancel: '取消', customSaved: '自訂預設已儲存', customUpdated: '自訂預設已更新', customDeleted: '自訂預設已刪除', saveAsTitle: '將當前設定另存為自訂預設', updateTitle: '更新目前自訂預設', removeTitle: '刪除目前自訂預設', fromPreset: '來自:', modifiedSuffix: ' · 已修改',
|
|
75
81
|
presetNameRequired: '請輸入預設名稱',
|
|
76
82
|
loading: '載入中…', unavailable: '設定不可用(宿主未註冊命名空間)',
|
|
@@ -87,7 +93,10 @@ window.__ModuleLoader__.load({
|
|
|
87
93
|
placeholder: 'Custom text: use “+ Insert info”, or type directly',
|
|
88
94
|
insertInfo: '+ Insert info', insertTitle: 'Insert info at cursor', preview: 'Preview: ',
|
|
89
95
|
skipSubagents: 'Skip subagent sessions',
|
|
96
|
+
pushMode: 'Push channel', pushDual: 'System + in-page', pushSystem: 'System only', pushToast: 'In-page only',
|
|
90
97
|
discard: 'Discard', save: 'Save', saving: 'Saving…', saved: 'Saved ✓ · refresh page to apply', saveFailed: 'Save failed: ', refresh: 'Refresh',
|
|
98
|
+
permTitle: 'Notification permission', permGranted: 'Granted: system notifications available', permDefault: 'Not yet granted', permDenied: 'Blocked by browser', permUnsupported: 'System notifications not supported here',
|
|
99
|
+
permDefaultHint: 'Click "Request"; if the browser shows no prompt, allow via the address-bar permissions icon', permDeniedHint: 'This browser blocked notifications for this site: click the address-bar permissions icon → Site settings → Notifications → Allow, then reload', permUnsupportedHint: 'System notifications require HTTPS or localhost; use "In-page only" instead', permRequest: 'Request',
|
|
91
100
|
saveAs: 'New', update: 'Edit', remove: 'Delete', presetNamePlaceholder: 'Preset name', confirm: 'OK', cancel: 'Cancel', customSaved: 'Custom preset saved', customUpdated: 'Custom preset updated', customDeleted: 'Custom preset deleted', saveAsTitle: 'Save current config as custom preset', updateTitle: 'Update current custom preset', removeTitle: 'Delete current custom preset', fromPreset: 'From: ', modifiedSuffix: ' · modified',
|
|
92
101
|
presetNameRequired: 'Please enter a preset name',
|
|
93
102
|
loading: 'Loading…', unavailable: 'Unavailable (namespace not registered on host)',
|
|
@@ -104,7 +113,10 @@ window.__ModuleLoader__.load({
|
|
|
104
113
|
placeholder: 'カスタム文面:「+ 情報を挿入」または直接入力',
|
|
105
114
|
insertInfo: '+ 情報を挿入', insertTitle: 'カーソル位置に情報を挿入', preview: 'プレビュー:',
|
|
106
115
|
skipSubagents: 'サブエージェントセッションをスキップ',
|
|
116
|
+
pushMode: '通知チャネル', pushDual: 'システム+ページ内', pushSystem: 'システムのみ', pushToast: 'ページ内のみ',
|
|
107
117
|
discard: '破棄', save: '保存', saving: '保存中…', saved: '保存しました ✓ · ページを再読み込み', saveFailed: '保存失敗:', refresh: '再読み込み',
|
|
118
|
+
permTitle: '通知権限', permGranted: '許可済み:システム通知が利用可能', permDefault: '未許可', permDenied: 'ブラウザがブロック', permUnsupported: 'この環境ではシステム通知を利用できません',
|
|
119
|
+
permDefaultHint: '「許可をリクエスト」をクリック。ブラウザに確認が出ない場合は、アドレスバーの権限アイコンから手動で許可', permDeniedHint: 'このサイトの通知がブロックされています:アドレスバー左の権限アイコン → サイト設定 → 通知 → 許可 → ページを再読み込み', permUnsupportedHint: 'システム通知には HTTPS または localhost が必要です。「ページ内のみ」を推奨', permRequest: '許可をリクエスト',
|
|
108
120
|
saveAs: '新規', update: '編集', remove: '削除', presetNamePlaceholder: 'プリセット名', confirm: '決定', cancel: 'キャンセル', customSaved: 'カスタムプリセットを保存しました', customUpdated: 'カスタムプリセットを更新しました', customDeleted: 'カスタムプリセットを削除しました', saveAsTitle: '現在の設定をカスタムプリセットとして保存', updateTitle: '現在のカスタムプリセットを更新', removeTitle: '現在のカスタムプリセットを削除', fromPreset: '由来:', modifiedSuffix: ' · 変更あり',
|
|
109
121
|
presetNameRequired: 'プリセット名を入力してください',
|
|
110
122
|
loading: '読み込み中…', unavailable: '利用不可(ホストに名前空間未登録)',
|
|
@@ -121,7 +133,10 @@ window.__ModuleLoader__.load({
|
|
|
121
133
|
placeholder: '사용자 지정 문구: "+ 정보 삽입" 또는 직접 입력',
|
|
122
134
|
insertInfo: '+ 정보 삽입', insertTitle: '커서 위치에 정보 삽입', preview: '미리보기: ',
|
|
123
135
|
skipSubagents: '하위 에이전트 세션 건너뛰기',
|
|
136
|
+
pushMode: '알림 채널', pushDual: '시스템+페이지 내', pushSystem: '시스템만', pushToast: '페이지 내만',
|
|
124
137
|
discard: '취소', save: '저장', saving: '저장 중…', saved: '저장됨 ✓ · 새로고침 후 적용', saveFailed: '저장 실패: ', refresh: '새로고침',
|
|
138
|
+
permTitle: '알림 권한', permGranted: '허용됨: 시스템 알림 사용 가능', permDefault: '아직 허용되지 않음', permDenied: '브라우저에서 차단됨', permUnsupported: '이 환경에서는 시스템 알림을 사용할 수 없음',
|
|
139
|
+
permDefaultHint: '「권한 요청」클릭. 브라우저에 확인이 없으면 주소 표시줄 권한 아이콘에서 수동 허용', permDeniedHint: '이 사이트의 알림이 차단되었습니다: 주소 표시줄 왼쪽 권한 아이콘 → 사이트 설정 → 알림 → 허용 후 새로고침', permUnsupportedHint: '시스템 알림은 HTTPS 또는 localhost가 필요합니다. 「페이지 내만」사용 권장', permRequest: '권한 요청',
|
|
125
140
|
saveAs: '추가', update: '수정', remove: '삭제', presetNamePlaceholder: '프리셋 이름', confirm: '확인', cancel: '취소', customSaved: '사용자 지정 프리셋 저장됨', customUpdated: '사용자 지정 프리셋 업데이트됨', customDeleted: '사용자 지정 프리셋 삭제됨', saveAsTitle: '현재 구성을 사용자 지정 프리셋으로 저장', updateTitle: '현재 사용자 지정 프리셋 업데이트', removeTitle: '현재 사용자 지정 프리셋 삭제', fromPreset: '출처: ', modifiedSuffix: ' · 수정됨',
|
|
126
141
|
presetNameRequired: '프리셋 이름을 입력하세요',
|
|
127
142
|
loading: '불러오는 중…', unavailable: '사용 불가(호스트에 네임스페이스 미등록)',
|
|
@@ -183,7 +198,7 @@ window.__ModuleLoader__.load({
|
|
|
183
198
|
var notice = readNoticeAny(id)
|
|
184
199
|
if (notice || Date.now() - start > 6000) {
|
|
185
200
|
retryTimer = null
|
|
186
|
-
notifyUser(pushTitle(name, notice ? '' : ' 已完成'), notice || (cwdLine ? cwdLine + ' · ' : '') + '详情见会话内系统消息')
|
|
201
|
+
notifyUser(pushTitle(name, notice ? '' : ' 已完成'), notice || (cwdLine ? cwdLine + ' · ' : '') + '详情见会话内系统消息', pushModeOf())
|
|
187
202
|
return
|
|
188
203
|
}
|
|
189
204
|
retryTimer = setTimeout(attempt, 400)
|
|
@@ -191,6 +206,17 @@ window.__ModuleLoader__.load({
|
|
|
191
206
|
attempt()
|
|
192
207
|
}
|
|
193
208
|
|
|
209
|
+
/** 推送通道:读设置快照;未设置/异常回落 dual(保持原双通道行为)。 */
|
|
210
|
+
function pushModeOf() {
|
|
211
|
+
try {
|
|
212
|
+
if (notifyScope) {
|
|
213
|
+
var st = notifyScope.getSnapshot()
|
|
214
|
+
if (st && st.value && ['dual', 'system', 'toast'].indexOf(st.value.pushMode) >= 0) return st.value.pushMode
|
|
215
|
+
}
|
|
216
|
+
} catch (e) { /* 默认 */ }
|
|
217
|
+
return 'dual'
|
|
218
|
+
}
|
|
219
|
+
|
|
194
220
|
/** 推送标题:用户模板({title} = 会话标题);无通知时附带完成标记。 */
|
|
195
221
|
function pushTitle(name, suffix) {
|
|
196
222
|
var tpl = '{title}'
|
|
@@ -327,12 +353,16 @@ window.__ModuleLoader__.load({
|
|
|
327
353
|
}
|
|
328
354
|
|
|
329
355
|
/**
|
|
330
|
-
*
|
|
331
|
-
*
|
|
356
|
+
* 推送:按 pushMode 决定通道——
|
|
357
|
+
* - dual(默认):系统通知(每次独立 tag,避免同 tag 替换/分组折叠导致后续弹窗不可见)+ 页内 toast 保底;
|
|
358
|
+
* - system:仅系统通知(QQ 浏览器等 Chromium 壳把 Notification 渲染成页内横幅时配合关闭 toast);
|
|
359
|
+
* - toast:仅页内 toast(Chromium 壳浏览器推不了 Windows 通知时用——不再调用 Notification API,
|
|
360
|
+
* 浏览器横幅/气泡不会出现,页内只有插件自己的小 toast)。
|
|
332
361
|
*/
|
|
333
|
-
function notifyUser(title, body) {
|
|
362
|
+
function notifyUser(title, body, pushMode) {
|
|
363
|
+
var mode = pushMode === 'system' || pushMode === 'toast' ? pushMode : 'dual'
|
|
334
364
|
try {
|
|
335
|
-
if (typeof Notification !== 'undefined') {
|
|
365
|
+
if (mode !== 'toast' && typeof Notification !== 'undefined') {
|
|
336
366
|
if (Notification.permission === 'granted') {
|
|
337
367
|
// 每次完成事件的独立 tag:不与前一次互相替换,也不被折叠成一个分组条目
|
|
338
368
|
var n = new Notification(title, { body: body, tag: PLUGIN_TAG + ':' + Date.now(), silent: false })
|
|
@@ -359,8 +389,7 @@ window.__ModuleLoader__.load({
|
|
|
359
389
|
}
|
|
360
390
|
}
|
|
361
391
|
} catch (e) { /* 环境不支持 Notification → toast 兜底 */ }
|
|
362
|
-
|
|
363
|
-
showToast(title, body)
|
|
392
|
+
if (mode !== 'system') showToast(title, body)
|
|
364
393
|
}
|
|
365
394
|
|
|
366
395
|
function log(message) {
|
|
@@ -564,7 +593,12 @@ window.__ModuleLoader__.load({
|
|
|
564
593
|
}
|
|
565
594
|
/** 扁平快照(用于 dirty 对比与模板打包)。 */
|
|
566
595
|
function flatOf(cur) {
|
|
567
|
-
var out = {
|
|
596
|
+
var out = {
|
|
597
|
+
language: LANG_IDS.indexOf(cur.language) >= 0 ? cur.language : 'zh',
|
|
598
|
+
titleTemplate: String(cur.titleTemplate ?? ''),
|
|
599
|
+
includeDuration: !!cur.includeDuration, includeUsage: !!cur.includeUsage,
|
|
600
|
+
pushMode: ['dual', 'system', 'toast'].indexOf(cur.pushMode) >= 0 ? cur.pushMode : 'dual',
|
|
601
|
+
}
|
|
568
602
|
for (var i = 0; i < REASON_FIELDS.length; i++) out['tpl-' + REASON_FIELDS[i]] = templateOf(cur, REASON_FIELDS[i])
|
|
569
603
|
return out
|
|
570
604
|
}
|
|
@@ -605,6 +639,69 @@ window.__ModuleLoader__.load({
|
|
|
605
639
|
h('span', null, labelText))
|
|
606
640
|
}
|
|
607
641
|
|
|
642
|
+
/**
|
|
643
|
+
* 通知权限状态区块:实时显示浏览器对 Notification API 的授权状态与下一步引导。
|
|
644
|
+
* - granted:系统通知可用(按 pushMode 生效);
|
|
645
|
+
* - default:未授权,提供「请求授权」按钮(用户手势内请求——Chromium 忽略非手势的自动请求);
|
|
646
|
+
* - denied:浏览器已屏蔽(如 Edge 对"不熟悉"站点的自动阻止),引导到地址栏手动允许;
|
|
647
|
+
* - undefined:非安全上下文(http://IP)或浏览器不支持,引导改用页内提示。
|
|
648
|
+
*/
|
|
649
|
+
function notificationPermissionRow(t) {
|
|
650
|
+
var statusText, hintText, statusColor = SUBTLE
|
|
651
|
+
var hasApi = typeof Notification !== 'undefined'
|
|
652
|
+
if (!hasApi) {
|
|
653
|
+
statusText = t.permUnsupported
|
|
654
|
+
hintText = t.permUnsupportedHint
|
|
655
|
+
} else if (Notification.permission === 'granted') {
|
|
656
|
+
statusText = t.permGranted
|
|
657
|
+
hintText = ''
|
|
658
|
+
statusColor = 'var(--dsw-alias-success, #2ecc71)'
|
|
659
|
+
} else if (Notification.permission === 'denied') {
|
|
660
|
+
statusText = t.permDenied
|
|
661
|
+
hintText = t.permDeniedHint
|
|
662
|
+
statusColor = 'var(--dsw-alias-warning, #e67e22)'
|
|
663
|
+
} else {
|
|
664
|
+
statusText = t.permDefault
|
|
665
|
+
hintText = t.permDefaultHint
|
|
666
|
+
}
|
|
667
|
+
void permTick // 依赖刷新计数:授权后重渲染读取最新状态
|
|
668
|
+
return h('div', {
|
|
669
|
+
key: 'perm',
|
|
670
|
+
style: { display: 'flex', flexDirection: 'column', gap: '6px', padding: '12px 0', borderTop: DIVIDER },
|
|
671
|
+
},
|
|
672
|
+
h('div', { style: { fontSize: '13px', color: 'var(--dsw-alias-label-secondary, inherit)' } }, t.permTitle),
|
|
673
|
+
h('div', { style: { display: 'flex', alignItems: 'center', gap: '8px', flexWrap: 'wrap' } },
|
|
674
|
+
h('span', { style: { fontSize: '12px', color: statusColor } }, statusText),
|
|
675
|
+
hasApi && Notification.permission === 'default'
|
|
676
|
+
? h('button', {
|
|
677
|
+
type: 'button', onClick: requestPermissionNow,
|
|
678
|
+
style: { appearance: 'none', font: 'inherit', fontSize: '12px', cursor: 'pointer', padding: '2px 10px', borderRadius: '999px', border: '1px solid var(--dsw-alias-border-l2, rgba(127,127,127,0.35))', background: 'none', color: 'var(--dsw-alias-label-secondary, inherit)' },
|
|
679
|
+
}, t.permRequest)
|
|
680
|
+
: null,
|
|
681
|
+
),
|
|
682
|
+
hintText
|
|
683
|
+
? h('div', { style: { fontSize: '12px', color: SUBTLE, lineHeight: 1.6, wordBreak: 'break-word' } }, hintText)
|
|
684
|
+
: null,
|
|
685
|
+
)
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/** 用户手势内请求通知权限(Chromium 忽略非手势的自动请求,必须由点击触发)。 */
|
|
689
|
+
function requestPermissionNow() {
|
|
690
|
+
try {
|
|
691
|
+
if (typeof Notification !== 'undefined' && Notification.permission === 'default') {
|
|
692
|
+
var req = Notification.requestPermission()
|
|
693
|
+
if (req && typeof req.then === 'function') {
|
|
694
|
+
req.then(function (p) {
|
|
695
|
+
log('user-gesture notification permission → ' + p)
|
|
696
|
+
setPermTick(permTick + 1)
|
|
697
|
+
}).catch(function () { setPermTick(permTick + 1) })
|
|
698
|
+
} else {
|
|
699
|
+
setPermTick(permTick + 1)
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
} catch (e) { /* noop */ }
|
|
703
|
+
}
|
|
704
|
+
|
|
608
705
|
/**
|
|
609
706
|
* Chip 模板编辑器(contentEditable):
|
|
610
707
|
* - 普通文字就是文字,动态信息渲染成内联胶囊({duration} 等代码永不露出);
|
|
@@ -846,6 +943,9 @@ window.__ModuleLoader__.load({
|
|
|
846
943
|
var nameState = useState('')
|
|
847
944
|
var newName = nameState[0]
|
|
848
945
|
var setName = nameState[1]
|
|
946
|
+
var permTickState = useState(0) // 通知权限状态刷新计数(请求授权后 setPermTick(+1) 重渲染)
|
|
947
|
+
var permTick = permTickState[0]
|
|
948
|
+
var setPermTick = permTickState[1]
|
|
849
949
|
|
|
850
950
|
var ready = snap.status === 'ready'
|
|
851
951
|
var value = (snap && snap.value) || {}
|
|
@@ -934,6 +1034,7 @@ window.__ModuleLoader__.load({
|
|
|
934
1034
|
scope.set('language', flat.language),
|
|
935
1035
|
scope.set('templates', templates),
|
|
936
1036
|
scope.set('titleTemplate', flat.titleTemplate),
|
|
1037
|
+
scope.set('pushMode', flat.pushMode),
|
|
937
1038
|
]
|
|
938
1039
|
Promise.all(tasks).then(function () {
|
|
939
1040
|
// 预设自动同步:
|
|
@@ -1041,6 +1142,14 @@ window.__ModuleLoader__.load({
|
|
|
1041
1142
|
}, LANG_OPTIONS.map(function (o) {
|
|
1042
1143
|
return darkOption({ value: o.id, key: o.id }, o.label)
|
|
1043
1144
|
})), 'language'),
|
|
1145
|
+
fieldRow(t.pushMode, h('select', {
|
|
1146
|
+
value: ['dual', 'system', 'toast'].indexOf(cur.pushMode) >= 0 ? cur.pushMode : 'dual',
|
|
1147
|
+
onChange: function (e) { setVal('pushMode', e.target.value) }, style: SELECT_STYLE,
|
|
1148
|
+
}, [
|
|
1149
|
+
darkOption({ value: 'dual', key: 'dual' }, t.pushDual),
|
|
1150
|
+
darkOption({ value: 'system', key: 'system' }, t.pushSystem),
|
|
1151
|
+
darkOption({ value: 'toast', key: 'toast' }, t.pushToast),
|
|
1152
|
+
]), 'pushMode'),
|
|
1044
1153
|
h('div', { key: 'title-row', style: { display: 'flex', flexDirection: 'column', gap: '6px', padding: '12px 0', borderTop: DIVIDER } },
|
|
1045
1154
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
|
|
1046
1155
|
h('div', { style: { fontSize: '13px', color: 'var(--dsw-alias-label-secondary, inherit)' } }, t.titleLabel),
|
|
@@ -1148,6 +1257,8 @@ window.__ModuleLoader__.load({
|
|
|
1148
1257
|
return h('span', { key: 'tk-' + field + '-' + idx }, t.samples[seg.key] || '')
|
|
1149
1258
|
})))
|
|
1150
1259
|
})
|
|
1260
|
+
// 通知权限状态区(footer 之上):授权状态 + 引导(Edge 自动屏蔽等场景一目了然)
|
|
1261
|
+
rows.push(notificationPermissionRow(t))
|
|
1151
1262
|
rows.push(h('div', {
|
|
1152
1263
|
key: 'footer',
|
|
1153
1264
|
style: { borderTop: DIVIDER, display: 'flex', justifyContent: 'flex-end', alignItems: 'center', gap: '8px', padding: '12px 0 4px' },
|
package/lib/index.js
CHANGED
|
@@ -77,6 +77,8 @@ const DEFAULT_SETTINGS = {
|
|
|
77
77
|
includeDuration: true,
|
|
78
78
|
includeUsage: true,
|
|
79
79
|
skipSubagents: true,
|
|
80
|
+
/** 推送通道:dual=系统通知+页内提示(默认);system=仅系统通知;toast=仅页内提示。 */
|
|
81
|
+
pushMode: 'dual',
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
/** 从任意输入规整为设置形状(容忍缺失/多余字段)。 */
|
|
@@ -94,6 +96,7 @@ function sanitizeSettings(raw) {
|
|
|
94
96
|
includeDuration: typeof src.includeDuration === 'boolean' ? src.includeDuration : true,
|
|
95
97
|
includeUsage: typeof src.includeUsage === 'boolean' ? src.includeUsage : true,
|
|
96
98
|
skipSubagents: typeof src.skipSubagents === 'boolean' ? src.skipSubagents : true,
|
|
99
|
+
pushMode: ['dual', 'system', 'toast'].includes(src.pushMode) ? src.pushMode : 'dual',
|
|
97
100
|
}
|
|
98
101
|
}
|
|
99
102
|
|
|
@@ -130,6 +133,7 @@ export function apply(ctx, config = {}) {
|
|
|
130
133
|
includeDuration: Schema.boolean().default(true),
|
|
131
134
|
includeUsage: Schema.boolean().default(true),
|
|
132
135
|
skipSubagents: Schema.boolean().default(true),
|
|
136
|
+
pushMode: Schema.union(['dual', 'system', 'toast']).default('dual'),
|
|
133
137
|
})
|
|
134
138
|
let attempts = 0
|
|
135
139
|
const tryRegister = () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@telosmaylx/dsh-session-notify",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "DSH session completion notifier: appends a plugin system message to the session log on turn/end and pushes browser notifications (Web Notification + toast), with a fully customizable official settings panel (presets, 5-language templates, cache-hit rate & tok/s from official projections).",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|