@telosmaylx/dsh-session-notify 0.1.9 → 0.1.11
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 +594 -0
- package/README.ja.md +594 -0
- package/README.ko.md +594 -0
- package/README.md +594 -583
- package/README.zh-TW.md +594 -0
- package/lib/client.js +953 -191
- package/lib/core.js +64 -42
- package/lib/index.js +86 -2
- package/package.json +74 -70
- package/scripts/tmp-i18n-check.mjs +70 -0
package/lib/core.js
CHANGED
|
@@ -14,11 +14,11 @@ export const LANGUAGES = ['zh', 'zh-tw', 'en', 'ja', 'ko']
|
|
|
14
14
|
* 键为 LANGUAGES 中的代码;缺省回落 zh。
|
|
15
15
|
*/
|
|
16
16
|
const LABELS = {
|
|
17
|
-
zh: { completed: '会话已完成', aborted: '会话已中止', blocked: '会话被阻塞', error: '会话出错', 'max-tokens': '
|
|
18
|
-
'zh-tw': { completed: '會話已完成', aborted: '會話已中止', blocked: '會話被阻塞', error: '會話出錯', 'max-tokens': '
|
|
19
|
-
en: { completed: 'Session completed', aborted: 'Session aborted', blocked: 'Session blocked', error: 'Session failed', 'max-tokens': 'Session hit the
|
|
20
|
-
ja: { completed: 'セッション完了', aborted: 'セッション中止', blocked: 'セッションがブロックされました', error: 'セッションエラー', 'max-tokens': '
|
|
21
|
-
ko: { completed: '세션 완료', aborted: '세션 중단됨', blocked: '세션 차단됨', error: '세션 오류', 'max-tokens': '
|
|
17
|
+
zh: { completed: '会话已完成', aborted: '会话已中止', blocked: '会话被阻塞', error: '会话出错', 'max-tokens': '会话达到上限', interrupted: '会话已中断', generic: '会话已结束', sep: ':', dur: '用时', use: '消耗' },
|
|
18
|
+
'zh-tw': { completed: '會話已完成', aborted: '會話已中止', blocked: '會話被阻塞', error: '會話出錯', 'max-tokens': '會話達到上限', interrupted: '會話已中斷', generic: '會話已結束', sep: ':', dur: '用時', use: '消耗' },
|
|
19
|
+
en: { completed: 'Session completed', aborted: 'Session aborted', blocked: 'Session blocked', error: 'Session failed', 'max-tokens': 'Session hit the cap', interrupted: 'Session interrupted', generic: 'Session ended', sep: ': ', dur: 'took', use: 'used' },
|
|
20
|
+
ja: { completed: 'セッション完了', aborted: 'セッション中止', blocked: 'セッションがブロックされました', error: 'セッションエラー', 'max-tokens': '上限に到達', interrupted: 'セッションが中断されました', generic: 'セッション終了', sep: ':', dur: '所要', use: '消費' },
|
|
21
|
+
ko: { completed: '세션 완료', aborted: '세션 중단됨', blocked: '세션 차단됨', error: '세션 오류', 'max-tokens': '한도 도달', interrupted: '세션 중단됨', generic: '세션 종료', sep: ': ', dur: '소요', use: '소모' },
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
function tableFor(language) {
|
|
@@ -30,11 +30,11 @@ function tableFor(language) {
|
|
|
30
30
|
* 优先级:titleTemplates[kind] > titleTemplate(全局模板)> 本表。
|
|
31
31
|
*/
|
|
32
32
|
const DEFAULT_TITLES = {
|
|
33
|
-
zh: { completed: '任务已完成', aborted: '任务已中止', blocked: '任务被阻塞', error: '任务出错', 'max-tokens': '
|
|
34
|
-
'zh-tw': { completed: '任務已完成', aborted: '任務已中止', blocked: '任務被阻塞', error: '任務出錯', 'max-tokens': '
|
|
35
|
-
en: { completed: 'Task completed', aborted: 'Task aborted', blocked: 'Task blocked', error: 'Task failed', 'max-tokens': 'Task hit the
|
|
36
|
-
ja: { completed: 'タスク完了', aborted: 'タスク中止', blocked: 'タスクがブロックされました', error: 'タスクエラー', 'max-tokens': '
|
|
37
|
-
ko: { completed: '작업 완료', aborted: '작업 중단됨', blocked: '작업 차단됨', error: '작업 오류', 'max-tokens': '
|
|
33
|
+
zh: { completed: '任务已完成', aborted: '任务已中止', blocked: '任务被阻塞', error: '任务出错', 'max-tokens': '任务达到上限' },
|
|
34
|
+
'zh-tw': { completed: '任務已完成', aborted: '任務已中止', blocked: '任務被阻塞', error: '任務出錯', 'max-tokens': '任務達到上限' },
|
|
35
|
+
en: { completed: 'Task completed', aborted: 'Task aborted', blocked: 'Task blocked', error: 'Task failed', 'max-tokens': 'Task hit the cap' },
|
|
36
|
+
ja: { completed: 'タスク完了', aborted: 'タスク中止', blocked: 'タスクがブロックされました', error: 'タスクエラー', 'max-tokens': '上限に到達' },
|
|
37
|
+
ko: { completed: '작업 완료', aborted: '작업 중단됨', blocked: '작업 차단됨', error: '작업 오류', 'max-tokens': '한도 도달' },
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
/**
|
|
@@ -52,7 +52,19 @@ export function renderTitle(kind, opts = {}, titleValue = '') {
|
|
|
52
52
|
const global = typeof opts.titleTemplate === 'string' && opts.titleTemplate.trim() !== '' ? opts.titleTemplate : ''
|
|
53
53
|
const fallback = (DEFAULT_TITLES[language] && DEFAULT_TITLES[language][kind]) || DEFAULT_TITLES.zh[kind] || '任务已完成'
|
|
54
54
|
const tpl = per || global || fallback
|
|
55
|
-
|
|
55
|
+
// {image}/{icon} 是正文媒体的开关标签,标题里若误写则剥除;其余信息位({duration}{usage}{error}{cache}{tps})
|
|
56
|
+
// 用会话数据替换(无数据则替换为空串),避免字面量泄漏到推送标题
|
|
57
|
+
const out = String(tpl)
|
|
58
|
+
.replace(/\{image\}/g, '')
|
|
59
|
+
.replace(/\{icon\}/g, '')
|
|
60
|
+
.replace(/\{label\}/g, '')
|
|
61
|
+
.replaceAll('{title}', titleValue)
|
|
62
|
+
.replaceAll('{duration}', opts.duration ?? '')
|
|
63
|
+
.replaceAll('{usage}', opts.usage ?? '')
|
|
64
|
+
.replaceAll('{error}', opts.error ?? '')
|
|
65
|
+
.replaceAll('{cache}', opts.cache ?? '')
|
|
66
|
+
.replaceAll('{tps}', opts.tps ?? '')
|
|
67
|
+
.trim()
|
|
56
68
|
return out || fallback
|
|
57
69
|
}
|
|
58
70
|
|
|
@@ -67,44 +79,44 @@ export function renderTitle(kind, opts = {}, titleValue = '') {
|
|
|
67
79
|
*/
|
|
68
80
|
const DEFAULT_PHRASES = {
|
|
69
81
|
zh: {
|
|
70
|
-
completed: '会话{titleBlock}
|
|
71
|
-
aborted: '会话{titleBlock}
|
|
72
|
-
blocked: '会话{titleBlock}
|
|
73
|
-
error: '会话{titleBlock}
|
|
74
|
-
'max-tokens': '会话{titleBlock}
|
|
75
|
-
generic: '会话{titleBlock}
|
|
82
|
+
completed: '会话{titleBlock}已完成,请点击查看。',
|
|
83
|
+
aborted: '会话{titleBlock}已中止,请点击查看。',
|
|
84
|
+
blocked: '会话{titleBlock}被阻塞,请点击查看。',
|
|
85
|
+
error: '会话{titleBlock}出错,请点击查看。',
|
|
86
|
+
'max-tokens': '会话{titleBlock}达到上限,请点击查看。',
|
|
87
|
+
generic: '会话{titleBlock}已结束,请点击查看。',
|
|
76
88
|
},
|
|
77
89
|
'zh-tw': {
|
|
78
|
-
completed: '會話{titleBlock}
|
|
79
|
-
aborted: '會話{titleBlock}
|
|
80
|
-
blocked: '會話{titleBlock}
|
|
81
|
-
error: '會話{titleBlock}
|
|
82
|
-
'max-tokens': '會話{titleBlock}
|
|
83
|
-
generic: '會話{titleBlock}
|
|
90
|
+
completed: '會話{titleBlock}已完成,請點擊查看。',
|
|
91
|
+
aborted: '會話{titleBlock}已中止,請點擊查看。',
|
|
92
|
+
blocked: '會話{titleBlock}被阻塞,請點擊查看。',
|
|
93
|
+
error: '會話{titleBlock}出錯,請點擊查看。',
|
|
94
|
+
'max-tokens': '會話{titleBlock}達到上限,請點擊查看。',
|
|
95
|
+
generic: '會話{titleBlock}已結束,請點擊查看。',
|
|
84
96
|
},
|
|
85
97
|
en: {
|
|
86
|
-
completed: 'Session {titleBlock} completed
|
|
87
|
-
aborted: 'Session {titleBlock} aborted.
|
|
88
|
-
blocked: 'Session {titleBlock} blocked.
|
|
89
|
-
error: 'Session {titleBlock} failed
|
|
90
|
-
'max-tokens': 'Session {titleBlock} hit the
|
|
91
|
-
generic: 'Session {titleBlock} ended.
|
|
98
|
+
completed: 'Session {titleBlock} completed. Click to view.',
|
|
99
|
+
aborted: 'Session {titleBlock} aborted. Click to view.',
|
|
100
|
+
blocked: 'Session {titleBlock} blocked. Click to view.',
|
|
101
|
+
error: 'Session {titleBlock} failed. Click to view.',
|
|
102
|
+
'max-tokens': 'Session {titleBlock} hit the cap. Click to view.',
|
|
103
|
+
generic: 'Session {titleBlock} ended. Click to view.',
|
|
92
104
|
},
|
|
93
105
|
ja: {
|
|
94
|
-
completed: 'セッション{titleBlock}
|
|
95
|
-
aborted: 'セッション{titleBlock}
|
|
96
|
-
blocked: 'セッション{titleBlock}
|
|
97
|
-
error: 'セッション{titleBlock}
|
|
98
|
-
'max-tokens': 'セッション{titleBlock}
|
|
99
|
-
generic: 'セッション{titleBlock}
|
|
106
|
+
completed: 'セッション{titleBlock}完了。クリックして表示。',
|
|
107
|
+
aborted: 'セッション{titleBlock}中止。クリックして表示。',
|
|
108
|
+
blocked: 'セッション{titleBlock}がブロックされました。クリックして表示。',
|
|
109
|
+
error: 'セッション{titleBlock}エラー。クリックして表示。',
|
|
110
|
+
'max-tokens': 'セッション{titleBlock}上限に到達。クリックして表示。',
|
|
111
|
+
generic: 'セッション{titleBlock}終了。クリックして表示。',
|
|
100
112
|
},
|
|
101
113
|
ko: {
|
|
102
|
-
completed: '세션{titleBlock}
|
|
103
|
-
aborted: '세션{titleBlock} 중단됨.
|
|
104
|
-
blocked: '세션{titleBlock} 차단됨.
|
|
105
|
-
error: '세션{titleBlock}
|
|
106
|
-
'max-tokens': '세션{titleBlock}
|
|
107
|
-
generic: '세션{titleBlock} 종료.
|
|
114
|
+
completed: '세션{titleBlock} 완료. 클릭하여 확인.',
|
|
115
|
+
aborted: '세션{titleBlock} 중단됨. 클릭하여 확인.',
|
|
116
|
+
blocked: '세션{titleBlock} 차단됨. 클릭하여 확인.',
|
|
117
|
+
error: '세션{titleBlock} 오류. 클릭하여 확인.',
|
|
118
|
+
'max-tokens': '세션{titleBlock} 한도 도달. 클릭하여 확인.',
|
|
119
|
+
generic: '세션{titleBlock} 종료. 클릭하여 확인.',
|
|
108
120
|
},
|
|
109
121
|
}
|
|
110
122
|
|
|
@@ -299,7 +311,7 @@ export function tpsOf(usage, ms) {
|
|
|
299
311
|
* @param {{ startedAt: number; endedAt: number; usage?: object | null; includeDuration?: boolean; includeUsage?: boolean; cacheValue?: string; tpsValue?: string; titleValue?: string }} info
|
|
300
312
|
* @param {{ language?: 'zh' | 'en'; templates?: Record<string, string> }} [custom] -
|
|
301
313
|
* language 切换文案语言;templates[reason] 非空时作为正文模板,
|
|
302
|
-
* 支持占位符:{label} {title} {duration} {usage} {error} {cache} {tps}
|
|
314
|
+
* 支持占位符:{label} {title} {duration} {usage} {error} {cache} {tps}({image} 渲染时剥除)。
|
|
303
315
|
* @returns {{ summary: string; text: string }}
|
|
304
316
|
*/
|
|
305
317
|
export function buildNotice(kind, reasonData, { startedAt, endedAt, usage = null, includeDuration = true, includeUsage = true, cacheValue, tpsValue, titleValue }, custom = {}) {
|
|
@@ -359,11 +371,19 @@ export function buildNotice(kind, reasonData, { startedAt, endedAt, usage = null
|
|
|
359
371
|
// 「会话已完成」,用户展开前完全看不到内容)。
|
|
360
372
|
summary: bound(text, 120),
|
|
361
373
|
text,
|
|
374
|
+
// 标题信息位数据(renderTitle 替换 {duration}{usage}{error}{cache}{tps} 用)
|
|
375
|
+
duration: durationLine ?? '',
|
|
376
|
+
usage: usageLine ?? '',
|
|
377
|
+
error: errText || 'none', // 无报错时显示 none,而非空串
|
|
378
|
+
cache: cacheLine ?? '',
|
|
379
|
+
tps: tpsLine ?? '',
|
|
362
380
|
}
|
|
363
381
|
}
|
|
364
382
|
|
|
365
383
|
/**
|
|
366
384
|
* 模板占位符替换:{label} 已全面移除(模板中直接剥除);支持 {duration} {usage} {error} {cache} {tps}。
|
|
385
|
+
* {image} {icon} 同样在正文渲染时剥除——它们只是「该原因通知带自定义大图/图标」的开关标签,
|
|
386
|
+
* 图片本体(data URI)存于设置文档 images[kind] / icons[kind],由客户端发通知时附加,绝不进入会话日志文本。
|
|
367
387
|
* @param {string} template
|
|
368
388
|
* @param {{ label: string; duration: string; usage: string; error: string; cache: string; tps: string }} vars
|
|
369
389
|
* @returns {string}
|
|
@@ -371,6 +391,8 @@ export function buildNotice(kind, reasonData, { startedAt, endedAt, usage = null
|
|
|
371
391
|
export function renderTemplate(template, vars) {
|
|
372
392
|
return String(template)
|
|
373
393
|
.replace(/\{label\}/g, '')
|
|
394
|
+
.replace(/\{image\}/g, '')
|
|
395
|
+
.replace(/\{icon\}/g, '')
|
|
374
396
|
.replaceAll('{title}', vars.title ?? '')
|
|
375
397
|
.replaceAll('{duration}', vars.duration)
|
|
376
398
|
.replaceAll('{usage}', vars.usage)
|
package/lib/index.js
CHANGED
|
@@ -81,6 +81,20 @@ const DEFAULT_SETTINGS = {
|
|
|
81
81
|
skipSubagents: true,
|
|
82
82
|
/** 推送通道:dual=系统通知+页内提示(默认);system=仅系统通知;toast=仅页内提示。 */
|
|
83
83
|
pushMode: 'dual',
|
|
84
|
+
/** 系统通知大图(Notification image,https URL):显示在通知卡片正文中/下方;空 = 不显示。 */
|
|
85
|
+
imageUrl: '',
|
|
86
|
+
/** 系统通知图标(Notification icon,https URL):空 = 浏览器默认(站点图标)。 */
|
|
87
|
+
iconUrl: '',
|
|
88
|
+
/** 完整预览版(等比 1024 未裁切,lightbox 查看原图用):与 imageUrl/iconUrl 对应,空 = 回退裁切版。 */
|
|
89
|
+
imagePreviewUrl: '',
|
|
90
|
+
iconPreviewUrl: '',
|
|
91
|
+
/** 按原因的自定义通知大图({image} 标签开关对应的数据,本地文件压缩后的 data URI)。 */
|
|
92
|
+
images: { completed: '', error: '', aborted: '', blocked: '', 'max-tokens': '' },
|
|
93
|
+
/** 按原因的自定义通知图标({icon} 标签开关对应的数据,本地文件压缩后的 data URI,128×128 方形)。 */
|
|
94
|
+
icons: { completed: '', error: '', aborted: '', blocked: '', 'max-tokens': '' },
|
|
95
|
+
/** 按原因的完整预览版(等比 1024 未裁切),与 images/icons 一一对应。 */
|
|
96
|
+
imagePreviews: { completed: '', error: '', aborted: '', blocked: '', 'max-tokens': '' },
|
|
97
|
+
iconPreviews: { completed: '', error: '', aborted: '', blocked: '', 'max-tokens': '' },
|
|
84
98
|
}
|
|
85
99
|
|
|
86
100
|
/** 从任意输入规整为设置形状(容忍缺失/多余字段)。 */
|
|
@@ -96,6 +110,26 @@ function sanitizeSettings(raw) {
|
|
|
96
110
|
for (const key of Object.keys(titleTemplates)) {
|
|
97
111
|
if (typeof titlesRaw[key] === 'string') titleTemplates[key] = titlesRaw[key]
|
|
98
112
|
}
|
|
113
|
+
const imagesRaw = src.images && typeof src.images === 'object' ? src.images : {}
|
|
114
|
+
const images = { ...DEFAULT_SETTINGS.images }
|
|
115
|
+
for (const key of Object.keys(images)) {
|
|
116
|
+
if (typeof imagesRaw[key] === 'string') images[key] = imagesRaw[key]
|
|
117
|
+
}
|
|
118
|
+
const iconsRaw = src.icons && typeof src.icons === 'object' ? src.icons : {}
|
|
119
|
+
const icons = { ...DEFAULT_SETTINGS.icons }
|
|
120
|
+
for (const key of Object.keys(icons)) {
|
|
121
|
+
if (typeof iconsRaw[key] === 'string') icons[key] = iconsRaw[key]
|
|
122
|
+
}
|
|
123
|
+
const imagePreviewsRaw = src.imagePreviews && typeof src.imagePreviews === 'object' ? src.imagePreviews : {}
|
|
124
|
+
const imagePreviews = { ...DEFAULT_SETTINGS.imagePreviews }
|
|
125
|
+
for (const key of Object.keys(imagePreviews)) {
|
|
126
|
+
if (typeof imagePreviewsRaw[key] === 'string') imagePreviews[key] = imagePreviewsRaw[key]
|
|
127
|
+
}
|
|
128
|
+
const iconPreviewsRaw = src.iconPreviews && typeof src.iconPreviews === 'object' ? src.iconPreviews : {}
|
|
129
|
+
const iconPreviews = { ...DEFAULT_SETTINGS.iconPreviews }
|
|
130
|
+
for (const key of Object.keys(iconPreviews)) {
|
|
131
|
+
if (typeof iconPreviewsRaw[key] === 'string') iconPreviews[key] = iconPreviewsRaw[key]
|
|
132
|
+
}
|
|
99
133
|
return {
|
|
100
134
|
language: ['zh', 'zh-tw', 'en', 'ja', 'ko'].includes(src.language) ? src.language : 'zh',
|
|
101
135
|
templates,
|
|
@@ -105,6 +139,14 @@ function sanitizeSettings(raw) {
|
|
|
105
139
|
includeUsage: typeof src.includeUsage === 'boolean' ? src.includeUsage : true,
|
|
106
140
|
skipSubagents: typeof src.skipSubagents === 'boolean' ? src.skipSubagents : true,
|
|
107
141
|
pushMode: ['dual', 'system', 'toast'].includes(src.pushMode) ? src.pushMode : 'dual',
|
|
142
|
+
imageUrl: typeof src.imageUrl === 'string' ? src.imageUrl : '',
|
|
143
|
+
iconUrl: typeof src.iconUrl === 'string' ? src.iconUrl : '',
|
|
144
|
+
imagePreviewUrl: typeof src.imagePreviewUrl === 'string' ? src.imagePreviewUrl : '',
|
|
145
|
+
iconPreviewUrl: typeof src.iconPreviewUrl === 'string' ? src.iconPreviewUrl : '',
|
|
146
|
+
images,
|
|
147
|
+
icons,
|
|
148
|
+
imagePreviews,
|
|
149
|
+
iconPreviews,
|
|
108
150
|
}
|
|
109
151
|
}
|
|
110
152
|
|
|
@@ -149,6 +191,38 @@ export function apply(ctx, config = {}) {
|
|
|
149
191
|
includeUsage: Schema.boolean().default(true),
|
|
150
192
|
skipSubagents: Schema.boolean().default(true),
|
|
151
193
|
pushMode: Schema.union(['dual', 'system', 'toast']).default('dual'),
|
|
194
|
+
imageUrl: Schema.string().default(''),
|
|
195
|
+
iconUrl: Schema.string().default(''),
|
|
196
|
+
imagePreviewUrl: Schema.string().default(''),
|
|
197
|
+
iconPreviewUrl: Schema.string().default(''),
|
|
198
|
+
images: Schema.object({
|
|
199
|
+
completed: Schema.string().default(''),
|
|
200
|
+
error: Schema.string().default(''),
|
|
201
|
+
aborted: Schema.string().default(''),
|
|
202
|
+
blocked: Schema.string().default(''),
|
|
203
|
+
'max-tokens': Schema.string().default(''),
|
|
204
|
+
}),
|
|
205
|
+
icons: Schema.object({
|
|
206
|
+
completed: Schema.string().default(''),
|
|
207
|
+
error: Schema.string().default(''),
|
|
208
|
+
aborted: Schema.string().default(''),
|
|
209
|
+
blocked: Schema.string().default(''),
|
|
210
|
+
'max-tokens': Schema.string().default(''),
|
|
211
|
+
}),
|
|
212
|
+
imagePreviews: Schema.object({
|
|
213
|
+
completed: Schema.string().default(''),
|
|
214
|
+
error: Schema.string().default(''),
|
|
215
|
+
aborted: Schema.string().default(''),
|
|
216
|
+
blocked: Schema.string().default(''),
|
|
217
|
+
'max-tokens': Schema.string().default(''),
|
|
218
|
+
}),
|
|
219
|
+
iconPreviews: Schema.object({
|
|
220
|
+
completed: Schema.string().default(''),
|
|
221
|
+
error: Schema.string().default(''),
|
|
222
|
+
aborted: Schema.string().default(''),
|
|
223
|
+
blocked: Schema.string().default(''),
|
|
224
|
+
'max-tokens': Schema.string().default(''),
|
|
225
|
+
}),
|
|
152
226
|
})
|
|
153
227
|
let attempts = 0
|
|
154
228
|
const tryRegister = () => {
|
|
@@ -285,8 +359,18 @@ export function apply(ctx, config = {}) {
|
|
|
285
359
|
tpsValue,
|
|
286
360
|
titleValue,
|
|
287
361
|
}, { language: settings.language, templates: settings.templates })
|
|
288
|
-
// 推送标题按原因渲染:titleTemplates[kind] > titleTemplate >
|
|
289
|
-
|
|
362
|
+
// 推送标题按原因渲染:titleTemplates[kind] > titleTemplate > 语言默认(如「任务已完成」);
|
|
363
|
+
// 标题里插的信息位({duration}{usage}{error}{cache}{tps})用会话数据替换,无则剥除
|
|
364
|
+
const title = renderTitle(kind, {
|
|
365
|
+
language: settings.language,
|
|
366
|
+
titleTemplate: settings.titleTemplate,
|
|
367
|
+
titleTemplates: settings.titleTemplates,
|
|
368
|
+
duration: notice.duration,
|
|
369
|
+
usage: notice.usage,
|
|
370
|
+
error: notice.error,
|
|
371
|
+
cache: notice.cache,
|
|
372
|
+
tps: notice.tps,
|
|
373
|
+
}, titleValue)
|
|
290
374
|
// 边界约束:session/event 观察者回调运行在 turn/end 那次 append 的
|
|
291
375
|
// 发布边界之内(dsh-session 在 dispatch 前 set entry.appending,
|
|
292
376
|
// finally 中复位),此时同步 append 会被拒绝:
|
package/package.json
CHANGED
|
@@ -1,70 +1,74 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@telosmaylx/dsh-session-notify",
|
|
3
|
-
"version": "0.1.
|
|
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
|
-
"private": false,
|
|
6
|
-
"type": "module",
|
|
7
|
-
"main": "./lib/index.js",
|
|
8
|
-
"exports": {
|
|
9
|
-
".": {
|
|
10
|
-
"default": "./lib/index.js"
|
|
11
|
-
},
|
|
12
|
-
"./client": {
|
|
13
|
-
"default": "./lib/client.js"
|
|
14
|
-
},
|
|
15
|
-
"./core": {
|
|
16
|
-
"default": "./lib/core.js"
|
|
17
|
-
},
|
|
18
|
-
"./package.json": "./package.json"
|
|
19
|
-
},
|
|
20
|
-
"files": [
|
|
21
|
-
"lib",
|
|
22
|
-
"scripts",
|
|
23
|
-
"cordis.patch.yml",
|
|
24
|
-
"README.md",
|
|
25
|
-
"
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
"
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
"
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
"url": "https://github.com/TelosmaYLX/dsh-session-notify
|
|
48
|
-
},
|
|
49
|
-
"
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@telosmaylx/dsh-session-notify",
|
|
3
|
+
"version": "0.1.11",
|
|
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
|
+
"private": false,
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "./lib/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"default": "./lib/index.js"
|
|
11
|
+
},
|
|
12
|
+
"./client": {
|
|
13
|
+
"default": "./lib/client.js"
|
|
14
|
+
},
|
|
15
|
+
"./core": {
|
|
16
|
+
"default": "./lib/core.js"
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"lib",
|
|
22
|
+
"scripts",
|
|
23
|
+
"cordis.patch.yml",
|
|
24
|
+
"README.md",
|
|
25
|
+
"README.en.md",
|
|
26
|
+
"README.zh-TW.md",
|
|
27
|
+
"README.ja.md",
|
|
28
|
+
"README.ko.md",
|
|
29
|
+
"LICENSE"
|
|
30
|
+
],
|
|
31
|
+
"publishConfig": {
|
|
32
|
+
"access": "public"
|
|
33
|
+
},
|
|
34
|
+
"keywords": [
|
|
35
|
+
"deepseek-harness",
|
|
36
|
+
"dsh",
|
|
37
|
+
"dsh-plugin",
|
|
38
|
+
"session-notify",
|
|
39
|
+
"notification",
|
|
40
|
+
"completion-reminder",
|
|
41
|
+
"agent"
|
|
42
|
+
],
|
|
43
|
+
"author": "dsh-session-notify contributors",
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/TelosmaYLX/dsh-session-notify.git"
|
|
48
|
+
},
|
|
49
|
+
"homepage": "https://github.com/TelosmaYLX/dsh-session-notify#readme",
|
|
50
|
+
"bugs": {
|
|
51
|
+
"url": "https://github.com/TelosmaYLX/dsh-session-notify/issues"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"cordis": ">=4.0.0-rc <5"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=22"
|
|
58
|
+
},
|
|
59
|
+
"dsh": {
|
|
60
|
+
"bundle": {
|
|
61
|
+
"patch": "./cordis.patch.yml"
|
|
62
|
+
},
|
|
63
|
+
"client": {
|
|
64
|
+
"inject": [
|
|
65
|
+
"@deepseek-ai/dsh-client-runtime"
|
|
66
|
+
],
|
|
67
|
+
"platform": "web"
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
"scripts": {
|
|
71
|
+
"build": "bash scripts/build.sh || node --check lib/index.js && node --check lib/core.js && node --check lib/client.js",
|
|
72
|
+
"prepublishOnly": "node --check lib/index.js && node --check lib/core.js && node --check lib/client.js"
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
// 检查 5 语言 i18n 键一致性:解析 lib/client.js 的 5 个语言块,对比键集合与空值
|
|
2
|
+
import { readFileSync } from 'node:fs'
|
|
3
|
+
|
|
4
|
+
const src = readFileSync('lib/client.js', 'utf8')
|
|
5
|
+
const lines = src.split('\n')
|
|
6
|
+
|
|
7
|
+
// 找到每个语言块的起始行
|
|
8
|
+
const LANG_KEYS = ['zh', "'zh-tw'", 'en', 'ja', 'ko']
|
|
9
|
+
const blocks = {}
|
|
10
|
+
for (let i = 0; i < lines.length; i++) {
|
|
11
|
+
const m = /^\s*(?:'([a-z-]+)'|([a-z]+))\s*:\s*\{/.exec(lines[i])
|
|
12
|
+
if (m) {
|
|
13
|
+
const key = m[1] || m[2]
|
|
14
|
+
// 只收集 I18N 主块(在 i18n 区域 40-180 行附近,避免误抓其他对象)
|
|
15
|
+
if (i < 200 && (LANG_KEYS.includes("'" + key + "'") || LANG_KEYS.includes(key))) {
|
|
16
|
+
// 读取到闭合的 }, 或 }, 或 }, 或
|
|
17
|
+
let block = ''
|
|
18
|
+
let j = i
|
|
19
|
+
let depth = 0
|
|
20
|
+
let started = false
|
|
21
|
+
for (; j < lines.length; j++) {
|
|
22
|
+
const l = lines[j]
|
|
23
|
+
for (const ch of l) {
|
|
24
|
+
if (ch === '{') { depth++; started = true }
|
|
25
|
+
else if (ch === '}') depth--
|
|
26
|
+
}
|
|
27
|
+
block += l + '\n'
|
|
28
|
+
if (started && depth <= 0) break
|
|
29
|
+
}
|
|
30
|
+
blocks[key] = block
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (Object.keys(blocks).length < 5) {
|
|
36
|
+
console.log('FAIL: 只找到语言块:', Object.keys(blocks))
|
|
37
|
+
process.exit(1)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 提取键:匹配 key: 'value' 或 key: "value"(含嵌套对象如 fields: {...} —— 只取顶层键)
|
|
41
|
+
function extractKeys(block) {
|
|
42
|
+
const keys = new Set()
|
|
43
|
+
const re = /^\s*([A-Za-z][A-Za-z0-9]*)\s*:/gm
|
|
44
|
+
let m
|
|
45
|
+
while ((m = re.exec(block))) keys.add(m[1])
|
|
46
|
+
// 去掉块首行(语言名,如 "zh: {" / "'zh-tw': {")
|
|
47
|
+
keys.delete('zh'); keys.delete('en'); keys.delete('ja'); keys.delete('ko')
|
|
48
|
+
return keys
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const zhKeys = extractKeys(blocks['zh'])
|
|
52
|
+
console.log('zh 键数量:', zhKeys.size)
|
|
53
|
+
|
|
54
|
+
let allOk = true
|
|
55
|
+
for (const lang of ['zh-tw', 'en', 'ja', 'ko']) {
|
|
56
|
+
const k = extractKeys(blocks[lang])
|
|
57
|
+
const missing = [...zhKeys].filter((x) => !k.has(x))
|
|
58
|
+
const extra = [...k].filter((x) => !zhKeys.has(x))
|
|
59
|
+
console.log(`\n[${lang}] 键数 ${k.size}`)
|
|
60
|
+
if (missing.length) { console.log(' 缺少(相对zh):', missing.join(', ')); allOk = false }
|
|
61
|
+
if (extra.length) console.log(' 多余(相对zh):', extra.join(', '))
|
|
62
|
+
// 检查关键值是否为空
|
|
63
|
+
for (const key of [...zhKeys]) {
|
|
64
|
+
const vm = new RegExp('\\b' + key + '\\s*:\\s*([\'"])(.*?)\\1').exec(blocks[lang])
|
|
65
|
+
if (vm && vm[2] === '' ) { console.log(` [${lang}] 键 ${key} 值为空!`); allOk = false }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
console.log('\n===== i18n 键一致性: ' + (allOk ? 'PASS' : 'FAIL') + ' =====')
|
|
70
|
+
process.exit(allOk ? 0 : 1)
|