@telosmaylx/dsh-session-notify 0.1.4 → 0.1.5
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 +6 -1
- package/lib/client.js +41 -8
- package/lib/index.js +4 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -292,10 +292,14 @@ Session completed (took 3m25s, used 1,240 in / 3,560 out).
|
|
|
292
292
|
| --- | --- |
|
|
293
293
|
| 预设 | 下拉选择内置或自定义预设;「新增」把当前配置另存为自定义预设;当前预设可「删除」 |
|
|
294
294
|
| 语言 | 5 种语言单选,切换即时重渲染整个面板 |
|
|
295
|
+
| 推送方式 | 三选一:双通道(系统通知 + 页内提示,默认)/ 仅系统通知 / 仅页内提示 |
|
|
295
296
|
| 推送标题 | `{title}` 引用会话标题,留空则直接用会话标题 |
|
|
296
297
|
| 模板 × 5 | 每条结束原因(完成、出错、中止、阻塞、输出上限)独立一个 Chip 编辑器:文字 + 内联信息胶囊,光标处插入、点击移除、实时预览 |
|
|
297
298
|
| 跳过子代理会话 | 复选框(保存时一并写入设置文档) |
|
|
298
|
-
| 保存 | 写入宿主设置文档(`language` / `templates` / `titleTemplate`);保存后显示「点击刷新」链接 |
|
|
299
|
+
| 保存 | 写入宿主设置文档(`language` / `templates` / `titleTemplate` / `pushMode`);保存后显示「点击刷新」链接 |
|
|
300
|
+
|
|
301
|
+
> [!NOTE]
|
|
302
|
+
> 「推送方式」的取舍:`dual`(默认)同时弹 Windows 系统通知与页内 toast,toast 是保底通道,防止系统通知被平台静默(专注助手、通知横幅关闭)。但 QQ 浏览器等 Chromium 壳浏览器会把 `Notification` 渲染成页内横幅而非 Windows 通知中心——此时 `dual` 会造成页内两个提示。这类浏览器请选「仅页内提示」(不再调用 `Notification`,页内只有一个小 toast);Firefox 另需在地址栏手动授权通知,且窗口聚焦时通知显示为页内横幅、失焦才进系统通知中心。
|
|
299
303
|
|
|
300
304
|
> [!NOTE]
|
|
301
305
|
> 面板中「跳过子代理会话」保存的是设置文档里的布尔值;宿主 `cordis.patch.yml` 的 `config.skipSubagents` 是其启动默认值,两者任一为真即跳过。
|
|
@@ -507,6 +511,7 @@ node scripts/verify-notice.mjs <session.jsonl.zstd>
|
|
|
507
511
|
|
|
508
512
|
| 版本 | 日期 | 变更 |
|
|
509
513
|
| --- | --- | --- |
|
|
514
|
+
| **0.1.5** | 2026-08-29 | 新增「推送方式」设置(双通道 / 仅系统通知 / 仅页内提示):解决 QQ 浏览器等 Chromium 壳把 `Notification` 渲染成页内横幅导致的双提示;`pushMode` 加入设置 schema 与设置面板 |
|
|
510
515
|
| **0.1.4** | 2026-08-28 | 补充完整卸载支持:`dispose` 生命周期收尾(host 置卸载标志抑制待追加微任务;client 清理正文轮询定时器、`__dsch_notify_debug` 钩子、toast 容器);卸载文档与 FAQ 同步 |
|
|
511
516
|
| **0.1.3** | 2026-08-28 | 声明官方 `dsh.bundle` manifest(`dsh plugin add` 一条命令自动挂载);settings 重试定时器改为 `ctx.effect()` 包装(Cordis effect 纪律);安装文档重排 |
|
|
512
517
|
| 0.1.2 | 2026-08-27 | 包更名至 `@telosmaylx` scope(npm 用户名作用域) |
|
package/lib/client.js
CHANGED
|
@@ -53,6 +53,7 @@ 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: '点击刷新',
|
|
57
58
|
saveAs: '新增', update: '修改', remove: '删除', presetNamePlaceholder: '预设名称', confirm: '确定', cancel: '取消', customSaved: '自定义预设已保存', customUpdated: '自定义预设已更新', customDeleted: '自定义预设已删除', saveAsTitle: '将当前配置另存为自定义预设', updateTitle: '更新当前自定义预设', removeTitle: '删除当前自定义预设', fromPreset: '来自:', modifiedSuffix: ' · 已修改',
|
|
58
59
|
presetNameRequired: '请输入预设名称',
|
|
@@ -70,6 +71,7 @@ window.__ModuleLoader__.load({
|
|
|
70
71
|
placeholder: '自訂文案:點「+ 插入資訊」,或直接輸入文字',
|
|
71
72
|
insertInfo: '+ 插入資訊', insertTitle: '在游標處插入資訊', preview: '預覽:',
|
|
72
73
|
skipSubagents: '跳過子代理會話',
|
|
74
|
+
pushMode: '推送方式', pushDual: '系統通知 + 頁內提示', pushSystem: '僅系統通知', pushToast: '僅頁內提示',
|
|
73
75
|
discard: '放棄', save: '儲存', saving: '儲存中…', saved: '已儲存 ✓ · 重新整理頁面後生效', saveFailed: '儲存失敗:', refresh: '點擊重新整理',
|
|
74
76
|
saveAs: '新增', update: '修改', remove: '刪除', presetNamePlaceholder: '預設名稱', confirm: '確定', cancel: '取消', customSaved: '自訂預設已儲存', customUpdated: '自訂預設已更新', customDeleted: '自訂預設已刪除', saveAsTitle: '將當前設定另存為自訂預設', updateTitle: '更新目前自訂預設', removeTitle: '刪除目前自訂預設', fromPreset: '來自:', modifiedSuffix: ' · 已修改',
|
|
75
77
|
presetNameRequired: '請輸入預設名稱',
|
|
@@ -87,6 +89,7 @@ window.__ModuleLoader__.load({
|
|
|
87
89
|
placeholder: 'Custom text: use “+ Insert info”, or type directly',
|
|
88
90
|
insertInfo: '+ Insert info', insertTitle: 'Insert info at cursor', preview: 'Preview: ',
|
|
89
91
|
skipSubagents: 'Skip subagent sessions',
|
|
92
|
+
pushMode: 'Push channel', pushDual: 'System + in-page', pushSystem: 'System only', pushToast: 'In-page only',
|
|
90
93
|
discard: 'Discard', save: 'Save', saving: 'Saving…', saved: 'Saved ✓ · refresh page to apply', saveFailed: 'Save failed: ', refresh: 'Refresh',
|
|
91
94
|
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
95
|
presetNameRequired: 'Please enter a preset name',
|
|
@@ -104,6 +107,7 @@ window.__ModuleLoader__.load({
|
|
|
104
107
|
placeholder: 'カスタム文面:「+ 情報を挿入」または直接入力',
|
|
105
108
|
insertInfo: '+ 情報を挿入', insertTitle: 'カーソル位置に情報を挿入', preview: 'プレビュー:',
|
|
106
109
|
skipSubagents: 'サブエージェントセッションをスキップ',
|
|
110
|
+
pushMode: '通知チャネル', pushDual: 'システム+ページ内', pushSystem: 'システムのみ', pushToast: 'ページ内のみ',
|
|
107
111
|
discard: '破棄', save: '保存', saving: '保存中…', saved: '保存しました ✓ · ページを再読み込み', saveFailed: '保存失敗:', refresh: '再読み込み',
|
|
108
112
|
saveAs: '新規', update: '編集', remove: '削除', presetNamePlaceholder: 'プリセット名', confirm: '決定', cancel: 'キャンセル', customSaved: 'カスタムプリセットを保存しました', customUpdated: 'カスタムプリセットを更新しました', customDeleted: 'カスタムプリセットを削除しました', saveAsTitle: '現在の設定をカスタムプリセットとして保存', updateTitle: '現在のカスタムプリセットを更新', removeTitle: '現在のカスタムプリセットを削除', fromPreset: '由来:', modifiedSuffix: ' · 変更あり',
|
|
109
113
|
presetNameRequired: 'プリセット名を入力してください',
|
|
@@ -121,6 +125,7 @@ window.__ModuleLoader__.load({
|
|
|
121
125
|
placeholder: '사용자 지정 문구: "+ 정보 삽입" 또는 직접 입력',
|
|
122
126
|
insertInfo: '+ 정보 삽입', insertTitle: '커서 위치에 정보 삽입', preview: '미리보기: ',
|
|
123
127
|
skipSubagents: '하위 에이전트 세션 건너뛰기',
|
|
128
|
+
pushMode: '알림 채널', pushDual: '시스템+페이지 내', pushSystem: '시스템만', pushToast: '페이지 내만',
|
|
124
129
|
discard: '취소', save: '저장', saving: '저장 중…', saved: '저장됨 ✓ · 새로고침 후 적용', saveFailed: '저장 실패: ', refresh: '새로고침',
|
|
125
130
|
saveAs: '추가', update: '수정', remove: '삭제', presetNamePlaceholder: '프리셋 이름', confirm: '확인', cancel: '취소', customSaved: '사용자 지정 프리셋 저장됨', customUpdated: '사용자 지정 프리셋 업데이트됨', customDeleted: '사용자 지정 프리셋 삭제됨', saveAsTitle: '현재 구성을 사용자 지정 프리셋으로 저장', updateTitle: '현재 사용자 지정 프리셋 업데이트', removeTitle: '현재 사용자 지정 프리셋 삭제', fromPreset: '출처: ', modifiedSuffix: ' · 수정됨',
|
|
126
131
|
presetNameRequired: '프리셋 이름을 입력하세요',
|
|
@@ -183,7 +188,7 @@ window.__ModuleLoader__.load({
|
|
|
183
188
|
var notice = readNoticeAny(id)
|
|
184
189
|
if (notice || Date.now() - start > 6000) {
|
|
185
190
|
retryTimer = null
|
|
186
|
-
notifyUser(pushTitle(name, notice ? '' : ' 已完成'), notice || (cwdLine ? cwdLine + ' · ' : '') + '详情见会话内系统消息')
|
|
191
|
+
notifyUser(pushTitle(name, notice ? '' : ' 已完成'), notice || (cwdLine ? cwdLine + ' · ' : '') + '详情见会话内系统消息', pushModeOf())
|
|
187
192
|
return
|
|
188
193
|
}
|
|
189
194
|
retryTimer = setTimeout(attempt, 400)
|
|
@@ -191,6 +196,17 @@ window.__ModuleLoader__.load({
|
|
|
191
196
|
attempt()
|
|
192
197
|
}
|
|
193
198
|
|
|
199
|
+
/** 推送通道:读设置快照;未设置/异常回落 dual(保持原双通道行为)。 */
|
|
200
|
+
function pushModeOf() {
|
|
201
|
+
try {
|
|
202
|
+
if (notifyScope) {
|
|
203
|
+
var st = notifyScope.getSnapshot()
|
|
204
|
+
if (st && st.value && ['dual', 'system', 'toast'].indexOf(st.value.pushMode) >= 0) return st.value.pushMode
|
|
205
|
+
}
|
|
206
|
+
} catch (e) { /* 默认 */ }
|
|
207
|
+
return 'dual'
|
|
208
|
+
}
|
|
209
|
+
|
|
194
210
|
/** 推送标题:用户模板({title} = 会话标题);无通知时附带完成标记。 */
|
|
195
211
|
function pushTitle(name, suffix) {
|
|
196
212
|
var tpl = '{title}'
|
|
@@ -327,12 +343,16 @@ window.__ModuleLoader__.load({
|
|
|
327
343
|
}
|
|
328
344
|
|
|
329
345
|
/**
|
|
330
|
-
*
|
|
331
|
-
*
|
|
346
|
+
* 推送:按 pushMode 决定通道——
|
|
347
|
+
* - dual(默认):系统通知(每次独立 tag,避免同 tag 替换/分组折叠导致后续弹窗不可见)+ 页内 toast 保底;
|
|
348
|
+
* - system:仅系统通知(QQ 浏览器等 Chromium 壳把 Notification 渲染成页内横幅时配合关闭 toast);
|
|
349
|
+
* - toast:仅页内 toast(Chromium 壳浏览器推不了 Windows 通知时用——不再调用 Notification API,
|
|
350
|
+
* 浏览器横幅/气泡不会出现,页内只有插件自己的小 toast)。
|
|
332
351
|
*/
|
|
333
|
-
function notifyUser(title, body) {
|
|
352
|
+
function notifyUser(title, body, pushMode) {
|
|
353
|
+
var mode = pushMode === 'system' || pushMode === 'toast' ? pushMode : 'dual'
|
|
334
354
|
try {
|
|
335
|
-
if (typeof Notification !== 'undefined') {
|
|
355
|
+
if (mode !== 'toast' && typeof Notification !== 'undefined') {
|
|
336
356
|
if (Notification.permission === 'granted') {
|
|
337
357
|
// 每次完成事件的独立 tag:不与前一次互相替换,也不被折叠成一个分组条目
|
|
338
358
|
var n = new Notification(title, { body: body, tag: PLUGIN_TAG + ':' + Date.now(), silent: false })
|
|
@@ -359,8 +379,7 @@ window.__ModuleLoader__.load({
|
|
|
359
379
|
}
|
|
360
380
|
}
|
|
361
381
|
} catch (e) { /* 环境不支持 Notification → toast 兜底 */ }
|
|
362
|
-
|
|
363
|
-
showToast(title, body)
|
|
382
|
+
if (mode !== 'system') showToast(title, body)
|
|
364
383
|
}
|
|
365
384
|
|
|
366
385
|
function log(message) {
|
|
@@ -564,7 +583,12 @@ window.__ModuleLoader__.load({
|
|
|
564
583
|
}
|
|
565
584
|
/** 扁平快照(用于 dirty 对比与模板打包)。 */
|
|
566
585
|
function flatOf(cur) {
|
|
567
|
-
var out = {
|
|
586
|
+
var out = {
|
|
587
|
+
language: LANG_IDS.indexOf(cur.language) >= 0 ? cur.language : 'zh',
|
|
588
|
+
titleTemplate: String(cur.titleTemplate ?? ''),
|
|
589
|
+
includeDuration: !!cur.includeDuration, includeUsage: !!cur.includeUsage,
|
|
590
|
+
pushMode: ['dual', 'system', 'toast'].indexOf(cur.pushMode) >= 0 ? cur.pushMode : 'dual',
|
|
591
|
+
}
|
|
568
592
|
for (var i = 0; i < REASON_FIELDS.length; i++) out['tpl-' + REASON_FIELDS[i]] = templateOf(cur, REASON_FIELDS[i])
|
|
569
593
|
return out
|
|
570
594
|
}
|
|
@@ -934,6 +958,7 @@ window.__ModuleLoader__.load({
|
|
|
934
958
|
scope.set('language', flat.language),
|
|
935
959
|
scope.set('templates', templates),
|
|
936
960
|
scope.set('titleTemplate', flat.titleTemplate),
|
|
961
|
+
scope.set('pushMode', flat.pushMode),
|
|
937
962
|
]
|
|
938
963
|
Promise.all(tasks).then(function () {
|
|
939
964
|
// 预设自动同步:
|
|
@@ -1041,6 +1066,14 @@ window.__ModuleLoader__.load({
|
|
|
1041
1066
|
}, LANG_OPTIONS.map(function (o) {
|
|
1042
1067
|
return darkOption({ value: o.id, key: o.id }, o.label)
|
|
1043
1068
|
})), 'language'),
|
|
1069
|
+
fieldRow(t.pushMode, h('select', {
|
|
1070
|
+
value: ['dual', 'system', 'toast'].indexOf(cur.pushMode) >= 0 ? cur.pushMode : 'dual',
|
|
1071
|
+
onChange: function (e) { setVal('pushMode', e.target.value) }, style: SELECT_STYLE,
|
|
1072
|
+
}, [
|
|
1073
|
+
darkOption({ value: 'dual', key: 'dual' }, t.pushDual),
|
|
1074
|
+
darkOption({ value: 'system', key: 'system' }, t.pushSystem),
|
|
1075
|
+
darkOption({ value: 'toast', key: 'toast' }, t.pushToast),
|
|
1076
|
+
]), 'pushMode'),
|
|
1044
1077
|
h('div', { key: 'title-row', style: { display: 'flex', flexDirection: 'column', gap: '6px', padding: '12px 0', borderTop: DIVIDER } },
|
|
1045
1078
|
h('div', { style: { display: 'flex', justifyContent: 'space-between', alignItems: 'center' } },
|
|
1046
1079
|
h('div', { style: { fontSize: '13px', color: 'var(--dsw-alias-label-secondary, inherit)' } }, t.titleLabel),
|
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.5",
|
|
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",
|