dsh-notify-yimit 0.2.1 → 0.2.3
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 +4 -3
- package/README.md +12 -2
- package/lib/client.js +146 -0
- package/lib/index.js +225 -32
- package/lib/toast-host.ps1 +487 -16
- package/lib/typert.host.js +28 -0
- package/package.json +4 -2
- package/scripts/check-prepublish.mjs +60 -0
package/README.en.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
A notification plugin for DeepSeek Harness: alerts you on **task completed / task failed / running (live activity) / awaiting approval / awaiting answer**.
|
|
18
18
|
|
|
19
19
|
- **Two notification styles**, switchable in Settings: **Windows system notifications** (native browser notifications) or **custom in-app notifications** (a toast at the bottom-right of the desktop, independent of the browser page); or turn everything **off** (default).
|
|
20
|
-
- **The notification title is the conversation title** (updated in place once the LLM generates it; the workspace directory name is never used). Toasts offer **Ignore**,
|
|
20
|
+
- **The notification title is the conversation title** (updated in place once the LLM generates it; the workspace directory name is never used). Toasts offer **Ignore**, on the web also **Jump to session**, and on the desktop also **Go to app** (see below).
|
|
21
21
|
- **One codebase for desktop and web**: the host detects its platform from the runtime and hides only browser-facing entry points — notification behavior is identical.
|
|
22
22
|
- **Settings-page integration**: DSH Settings → "Notifications" section with a master switch, notification style, toast count/duration, and per-type background/text colors (done = green, failed = red, running = blue, approval = yellow, question = purple).
|
|
23
23
|
|
|
@@ -38,11 +38,12 @@ A notification plugin for DeepSeek Harness: alerts you on **task completed / tas
|
|
|
38
38
|
- Custom notifications: **max simultaneously visible**, **display duration**, and a **per-type background/text color** list
|
|
39
39
|
(done = green, failed = red, running = blue, approval = yellow, question = purple; each customizable);
|
|
40
40
|
- The custom-settings block animates open/closed; system notifications offer one-click permission request.
|
|
41
|
-
- **Custom notification = desktop toast (independent of the browser)**: the host plugin spawns a **resident PowerShell + WPF host process** that pops borderless, always-on-top toasts at the **bottom-right** of the screen (multiple toasts stack upward without overlapping). Each toast has a title, body, and an **Ignore** button (**web only: plus a "Jump to session" button**; 10px rounded-rect buttons), colored per type from the config. Done/failed toasts auto-dismiss after the display duration; **running/approval/question toasts stay until their state ends** (turn end / decision made / answer given). Running content is **updated in place** (400ms throttle — no re-spawn, no flicker); approval/question are stateful and **not debounced** (multiple approvals/questions within 2s are never swallowed). Whether the browser page is open or minimized does not matter. Until the session title arrives (LLM-generated asynchronously — first-turn events precede the title event), toasts show a localized placeholder ("(Unnamed session)"); when the `session/title` event lands, **the titles of already-shown toasts are updated in place** — the workspace directory name is never used.
|
|
41
|
+
- **Custom notification = desktop toast (independent of the browser)**: the host plugin spawns a **resident PowerShell + WPF host process** that pops borderless, always-on-top toasts at the **bottom-right** of the screen (multiple toasts stack upward without overlapping). Each toast has a title, body, and an **Ignore** button (**web only: plus a "Jump to session" button**; **desktop only: plus a "Go to app" button**; 10px rounded-rect buttons), colored per type from the config. Done/failed toasts auto-dismiss after the display duration; **running/approval/question toasts stay until their state ends** (turn end / decision made / answer given). Running content is **updated in place** (400ms throttle — no re-spawn, no flicker); approval/question are stateful and **not debounced** (multiple approvals/questions within 2s are never swallowed). Whether the browser page is open or minimized does not matter. Until the session title arrives (LLM-generated asynchronously — first-turn events precede the title event), toasts show a localized placeholder ("(Unnamed session)"); when the `session/title` event lands, **the titles of already-shown toasts are updated in place** — the workspace directory name is never used.
|
|
42
42
|
- **Resident host architecture**: the plugin starts one `powershell` host process (`toast-host.ps1`) on load; WPF is loaded only once and every toast is created inside that process — toast creation latency drops from ~1s cold start to ~10ms. The host receives one JSON command per line on **stdin** (`show`/`text`/`title`/`move`/`close`/`shutdown`) and reports `pos`/`exit` on **stdout**; no per-toast process spawn and no ctl/pos file polling.
|
|
43
43
|
- **Jump to session (web only)**: clicking a system notification or the toast's "Jump to session" button opens the browser and navigates to the session (via the URL hash convention `#dsh-notify-yimit/session=<id>`, listened to by the client; optionally with a specific browser). **The desktop app does not show that button** — opening a browser from inside the desktop app is redundant.
|
|
44
|
+
- **Go to app (desktop only)**: the "Go to app" button (right of "Ignore") brings the running desktop window to the foreground. The **host process enumerates windows and activates it itself** (locating PIDs by the app executable path, picking that process's top-level window, then `ShowWindow(SW_RESTORE)` + `SetForegroundWindow`). It deliberately does **not** use the app's registered `dsh://open` protocol: the app only handles that URL in its **macOS `open-url`** event, while on Windows a protocol URL arrives as an argv argument and the app's `second-instance` handler neither reads argv nor checks the URL — that path is dead code on Windows. Window selection is tiered (visible + titled + unowned → titled + unowned → titled), so it still finds the main window when the app is **minimized to tray or covered**. The host is the foreground process at click time, so the "only the foreground process may steal focus" restriction does not apply. **Clicking it does not dismiss the toast** (unlike the other buttons); if no window can be located it silently does nothing.
|
|
44
45
|
- **Runtime-aware UI**: the host detects its platform from the runtime (Electron runtime = desktop, plain Node = web) and adapts the UI:
|
|
45
|
-
- desktop: toasts omit the "Jump to session" button; the settings page hides the jump browser picker and the "system notifications come from the browser / custom toasts sit at the bottom-right" explainer;
|
|
46
|
+
- desktop: toasts omit the "Jump to session" button and show "Go to app" instead; the settings page hides the jump browser picker and the "system notifications come from the browser / custom toasts sit at the bottom-right" explainer;
|
|
46
47
|
- web: everything stays visible.
|
|
47
48
|
- **System notifications**: native browser notifications; clicking one focuses the window and opens the session.
|
|
48
49
|
- **Requirements**: Windows (PowerShell 5.1+, built-in); custom desktop toasts need no extra dependencies.
|
package/README.md
CHANGED
|
@@ -45,7 +45,8 @@ DeepSeek Harness 通知插件:在 **任务完成 / 任务出错 / 运行中(实
|
|
|
45
45
|
- 自定义设置区块显隐带过渡动画;系统通知可一键申请浏览器通知权限。
|
|
46
46
|
- **自定义通知 = 桌面浮窗(不依赖浏览器)**:宿主插件通过**常驻 PowerShell + WPF 宿主进程**
|
|
47
47
|
在屏幕**右下角**弹出无边框置顶浮窗(多浮窗自动向上堆叠不重叠),标题、内容、**忽略**按钮
|
|
48
|
-
(**web
|
|
48
|
+
(**web 端另有「跳转会话」按钮**,**桌面端另有「返回应用」按钮**,按钮为圆角矩形 10px);
|
|
49
|
+
按通知类型使用对应配置的背景/文字色。完成/出错
|
|
49
50
|
按显示时长后自动消失;**运行中/待审批/待回答在状态结束前不会消失**(任务结束/审批决定/
|
|
50
51
|
回答完成后自动关闭)。运行中内容**原地更新文本**(400ms 节流,不重弹、不闪烁);
|
|
51
52
|
待审批/待回答为状态性通知,**不做防抖**(2s 内多条审批/提问不会被吞)。浏览器页面是否
|
|
@@ -59,9 +60,18 @@ DeepSeek Harness 通知插件:在 **任务完成 / 任务出错 / 运行中(实
|
|
|
59
60
|
- **跳转会话(仅 web 端)**:系统通知点击 / 自定义浮窗「跳转会话」按钮 → **打开浏览器**并定位到该会话
|
|
60
61
|
(通过 URL hash 约定 `#dsh-notify-yimit/session=<id>`,客户端监听后切换;可用所选浏览器打开)。
|
|
61
62
|
**桌面端(App)不显示该按钮**——在桌面应用里再打开浏览器属于多余动作。
|
|
63
|
+
- **返回应用(仅桌面端)**:浮窗「忽略」右侧的「返回应用」按钮把已运行的桌面窗口切到前台。
|
|
64
|
+
实现是**宿主进程自己枚举窗口并激活**(按应用可执行文件路径定位 PID,再挑出该进程的顶层窗口,
|
|
65
|
+
`ShowWindow(SW_RESTORE)` + `SetForegroundWindow`)。之所以不用应用已注册的 `dsh://open` 协议:
|
|
66
|
+
应用只在 **macOS 的 `open-url` 事件**里处理该 URL,Windows 上协议 URL 是作为 argv 交给新进程的,
|
|
67
|
+
而它的 `second-instance` 处理器不读 argv、也不校验 URL —— 那条路在 Windows 上是死代码。
|
|
68
|
+
窗口挑选分三级(可见+有标题+非 owner → 有标题+非 owner → 有标题),因此**最小化到托盘、
|
|
69
|
+
被其他窗口遮挡**时同样能找到主窗口。宿主被点击的那一刻本就是前台进程,所以不受
|
|
70
|
+
"仅前台进程可抢占前台"的限制。**点击后不关闭该通知**,浮窗保留(与其他按钮不同);
|
|
71
|
+
定位不到窗口时静默无动作。
|
|
62
72
|
- **运行环境自适应**:宿主按运行时自动判定平台(Electron 运行时 = 桌面端,普通 Node = web 端),
|
|
63
73
|
并据此调整界面:
|
|
64
|
-
-
|
|
74
|
+
- 桌面端:浮窗隐藏「跳转会话」按钮、改为显示「返回应用」按钮;设置页隐藏「跳转浏览器选择器」与"系统通知由浏览器弹出/
|
|
65
75
|
自定义为桌面右下角浮窗"那段说明;
|
|
66
76
|
- web 端:全部保留。
|
|
67
77
|
- **系统通知**:浏览器原生通知,点击通知即聚焦窗口并打开对应会话。
|
package/lib/client.js
CHANGED
|
@@ -78,6 +78,17 @@ window.__ModuleLoader__.load({
|
|
|
78
78
|
'.dn-msg.ok{color:var(--dsw-alias-state-success-primary)}',
|
|
79
79
|
'.dn-msg.err{color:var(--dsw-alias-state-error-primary)}',
|
|
80
80
|
'.dn-divider{height:1px;background:var(--dsw-alias-border-l1)}',
|
|
81
|
+
// ④ 宿主故障横幅:让"通知不显示"从静默失败变成可见、可解释。
|
|
82
|
+
// 颜色走 --dsw-alias-state-* 令牌,深/浅色模式都能自适应。
|
|
83
|
+
'.dn-host{display:flex;flex-direction:column;gap:8px;border:1px solid var(--dsw-alias-state-error-primary);border-radius:8px;padding:10px 12px;background:color-mix(in srgb,var(--dsw-alias-state-error-primary) 10%,transparent)}',
|
|
84
|
+
'.dn-host-line{display:flex;align-items:flex-start;gap:8px;font-size:12px;line-height:18px}',
|
|
85
|
+
'.dn-host-icon{flex:none;font-size:13px;line-height:18px;color:var(--dsw-alias-state-error-primary)}',
|
|
86
|
+
'.dn-host-title{font-weight:600;color:var(--dsw-alias-state-error-primary)}',
|
|
87
|
+
'.dn-host-body{color:var(--dsw-alias-label-secondary);flex:1;min-width:0}',
|
|
88
|
+
'.dn-host-actions{display:flex;align-items:center;gap:8px;padding-left:21px}',
|
|
89
|
+
'.dn-host-detail{font-size:11px;line-height:16px;font-family:var(--dsw-font-mono);color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l1);border-radius:6px;padding:6px 8px;margin-left:21px;max-height:88px;overflow:auto;white-space:pre-wrap;word-break:break-all}',
|
|
90
|
+
'.dn-host[data-tone="ok"]{border-color:var(--dsw-alias-state-success-primary);background:color-mix(in srgb,var(--dsw-alias-state-success-primary) 10%,transparent)}',
|
|
91
|
+
'.dn-host[data-tone="ok"] .dn-host-icon,.dn-host[data-tone="ok"] .dn-host-title{color:var(--dsw-alias-state-success-primary)}',
|
|
81
92
|
'.dn-layer{position:fixed;top:16px;right:16px;z-index:9999;display:flex;flex-direction:column;align-items:flex-end;gap:10px;pointer-events:none}',
|
|
82
93
|
'.dn-card{pointer-events:auto;width:320px;max-width:calc(100vw - 32px);border-radius:12px;box-shadow:var(--dsw-shadow-lv3);border:1px solid var(--dsw-alias-border-l2);background:var(--dn-bg,#20272f);color:var(--dn-fg,#e6edf3);overflow:hidden;animation:dn-in .18s ease}',
|
|
83
94
|
'@keyframes dn-in{from{opacity:0;transform:translateX(24px)}to{opacity:1;transform:translateX(0)}}',
|
|
@@ -131,6 +142,20 @@ window.__ModuleLoader__.load({
|
|
|
131
142
|
permissionDefault: '系统通知需要浏览器通知权限',
|
|
132
143
|
requestPermission: '申请通知权限',
|
|
133
144
|
triggerNote: '触发场景:任务完成、任务出错、运行中(实时内容)、等待审批、等待回答。通知标题为对话标题;点击通知或跳转按钮可打开对应会话。',
|
|
145
|
+
// ④ 宿主故障横幅
|
|
146
|
+
hostErrTitle: '通知宿主异常退出',
|
|
147
|
+
hostErrBody: '自定义通知当前无法显示{detail}。',
|
|
148
|
+
hostErrPending: ' —— 已积压 {n} 条通知',
|
|
149
|
+
hostErrRetryIn: ';正在退避,约 {s} 秒后自动重试',
|
|
150
|
+
hostErrRetryNow: '立即重试',
|
|
151
|
+
hostErrRetry: '重新拉起宿主',
|
|
152
|
+
hostErrDetail: '查看详情',
|
|
153
|
+
hostErrDetailHide: '收起详情',
|
|
154
|
+
hostErrDetailText: '退出码 {code},连续失败 {n} 次',
|
|
155
|
+
hostErrSignal: '退出码 {code}(信号 {signal}),连续失败 {n} 次',
|
|
156
|
+
hostErrOkTitle: '通知宿主已恢复',
|
|
157
|
+
hostErrOkBody: '自定义通知已恢复正常。',
|
|
158
|
+
hostErrOkBodyPending: '已补发 {n} 条积压通知。',
|
|
134
159
|
toastStyleTitle: '通知窗口样式',
|
|
135
160
|
toastGapLabel: '通知间距(px)',
|
|
136
161
|
enterEdit: '进入编辑模式',
|
|
@@ -177,6 +202,20 @@ window.__ModuleLoader__.load({
|
|
|
177
202
|
permissionDefault: 'System notifications need browser notification permission',
|
|
178
203
|
requestPermission: 'Request permission',
|
|
179
204
|
triggerNote: 'Triggers: task completed, task failed, running (live activity), awaiting approval, awaiting answer. The title is the conversation title; clicking the notification or the jump button opens the session.',
|
|
205
|
+
// ④ host-failure banner
|
|
206
|
+
hostErrTitle: 'Notification host exited unexpectedly',
|
|
207
|
+
hostErrBody: 'Custom notifications cannot be shown right now{detail}.',
|
|
208
|
+
hostErrPending: ' — {n} notification(s) queued',
|
|
209
|
+
hostErrRetryIn: '; backing off, retrying in about {s}s',
|
|
210
|
+
hostErrRetryNow: 'Retry now',
|
|
211
|
+
hostErrRetry: 'Restart host',
|
|
212
|
+
hostErrDetail: 'Show details',
|
|
213
|
+
hostErrDetailHide: 'Hide details',
|
|
214
|
+
hostErrDetailText: 'exit code {code}, {n} consecutive failure(s)',
|
|
215
|
+
hostErrSignal: 'exit code {code} (signal {signal}), {n} consecutive failure(s)',
|
|
216
|
+
hostErrOkTitle: 'Notification host recovered',
|
|
217
|
+
hostErrOkBody: 'Custom notifications are working again.',
|
|
218
|
+
hostErrOkBodyPending: '{n} queued notification(s) delivered.',
|
|
180
219
|
toastStyleTitle: 'Toast window style',
|
|
181
220
|
toastGapLabel: 'Toast gap (px)',
|
|
182
221
|
enterEdit: 'Enter edit mode',
|
|
@@ -356,10 +395,27 @@ window.__ModuleLoader__.load({
|
|
|
356
395
|
|
|
357
396
|
function parseState(v, path) {
|
|
358
397
|
if (v === null || typeof v !== 'object' || Array.isArray(v)) fail(path)
|
|
398
|
+
// ④ 宿主故障状态。旧宿主没有该字段 → null(不显示横幅)。
|
|
399
|
+
// 手写 codec 是"只取已知字段"的宽容解析,多余字段会被忽略,
|
|
400
|
+
// 但宿主侧 typert 的 z.object 会**丢弃**未声明字段,所以那边也必须声明。
|
|
401
|
+
let hostError = null
|
|
402
|
+
const he = v.hostError
|
|
403
|
+
if (he !== null && he !== undefined && typeof he === 'object' && !Array.isArray(he)) {
|
|
404
|
+
hostError = {
|
|
405
|
+
code: typeof he.code === 'number' ? he.code : null,
|
|
406
|
+
signal: typeof he.signal === 'string' ? he.signal : null,
|
|
407
|
+
at: typeof he.at === 'number' ? he.at : 0,
|
|
408
|
+
count: typeof he.count === 'number' ? he.count : 1,
|
|
409
|
+
retryAt: typeof he.retryAt === 'number' ? he.retryAt : 0,
|
|
410
|
+
}
|
|
411
|
+
}
|
|
359
412
|
return {
|
|
360
413
|
config: parseConfig(v.config, path + '.config'),
|
|
361
414
|
// 宿主运行环境:true = 桌面端。缺省按 web 端处理(旧宿主无该字段)。
|
|
362
415
|
desktop: v.desktop === true,
|
|
416
|
+
hostError,
|
|
417
|
+
pendingCount: typeof v.pendingCount === 'number' && v.pendingCount >= 0 ? Math.floor(v
|
|
418
|
+
.pendingCount) : 0,
|
|
363
419
|
events: Array.isArray(v.events) ? v.events.map((e, i) => parseEvent(e, path + '.events[' +
|
|
364
420
|
i + ']')) : []
|
|
365
421
|
}
|
|
@@ -474,6 +530,16 @@ window.__ModuleLoader__.load({
|
|
|
474
530
|
},
|
|
475
531
|
parameters: [],
|
|
476
532
|
result: strictResult('dsh-notify-yimit#AckResult', ackCodec)
|
|
533
|
+
}, {
|
|
534
|
+
id: 'dsh-notify-yimit#notify/retryHost',
|
|
535
|
+
service: 'notify',
|
|
536
|
+
namespace: 'notify',
|
|
537
|
+
method: 'retryHost',
|
|
538
|
+
invocation: {
|
|
539
|
+
kind: 'direct'
|
|
540
|
+
},
|
|
541
|
+
parameters: [],
|
|
542
|
+
result: strictResult('dsh-notify-yimit#AckResult', ackCodec)
|
|
477
543
|
}]
|
|
478
544
|
}
|
|
479
545
|
|
|
@@ -714,6 +780,22 @@ window.__ModuleLoader__.load({
|
|
|
714
780
|
.permission : 'unsupported')
|
|
715
781
|
// 通知窗口编辑模式(样板窗拖拽/缩放)
|
|
716
782
|
const [editing, setEditing] = useState(false)
|
|
783
|
+
// ④ 横幅里的「查看详情」折叠状态
|
|
784
|
+
const [hostDetail, setHostDetail] = useState(false)
|
|
785
|
+
// ④ 宿主故障横幅:退避倒计时每秒重算一次(依赖的 retryAt 是稳定数字,
|
|
786
|
+
// 所以这个 effect 不会因为轮询重渲染而反复重建定时器)。
|
|
787
|
+
const hostError = state?.hostError ?? null
|
|
788
|
+
const pendingCount = state?.pendingCount ?? 0
|
|
789
|
+
const retryAt = hostError !== null && typeof hostError.retryAt === 'number' ? hostError.retryAt : 0
|
|
790
|
+
const [nowTick, setNowTick] = useState(0)
|
|
791
|
+
useEffect(() => {
|
|
792
|
+
if (retryAt <= 0) return undefined
|
|
793
|
+
const timer = setInterval(() => setNowTick((n) => n + 1), 1000)
|
|
794
|
+
return () => clearInterval(timer)
|
|
795
|
+
}, [retryAt])
|
|
796
|
+
// 倒计时秒数(取整);nowTick 参与依赖以触发重算
|
|
797
|
+
void nowTick
|
|
798
|
+
const retryInSec = retryAt > 0 ? Math.max(0, Math.round((retryAt - Date.now()) / 1000)) : 0
|
|
717
799
|
// 最新值镜像:卸载清理时读取(避免空依赖闭包拿到陈旧值)。
|
|
718
800
|
const editingRef = useRef(false)
|
|
719
801
|
editingRef.current = editing
|
|
@@ -1084,6 +1166,52 @@ window.__ModuleLoader__.load({
|
|
|
1084
1166
|
el('p', {
|
|
1085
1167
|
className: 'dn-note'
|
|
1086
1168
|
}, t('saveHint')),
|
|
1169
|
+
// ④ 宿主故障横幅:让"自定义通知不显示"从静默失败变成可见、可解释。
|
|
1170
|
+
// 仅在宿主确实异常退出时出现(hostError 由宿主侧置上)。
|
|
1171
|
+
hostError !== null ? el('div', {
|
|
1172
|
+
className: 'dn-host',
|
|
1173
|
+
'data-tone': 'err'
|
|
1174
|
+
},
|
|
1175
|
+
el('div', {
|
|
1176
|
+
className: 'dn-host-line'
|
|
1177
|
+
},
|
|
1178
|
+
el('span', {
|
|
1179
|
+
className: 'dn-host-icon'
|
|
1180
|
+
}, '✕'),
|
|
1181
|
+
el('span', {
|
|
1182
|
+
className: 'dn-host-body'
|
|
1183
|
+
},
|
|
1184
|
+
el('span', {
|
|
1185
|
+
className: 'dn-host-title'
|
|
1186
|
+
}, t('hostErrTitle')),
|
|
1187
|
+
t('hostErrBody', {
|
|
1188
|
+
detail: (pendingCount > 0 ? t('hostErrPending', {
|
|
1189
|
+
n: pendingCount
|
|
1190
|
+
}) : '') + (retryInSec > 0 ? t('hostErrRetryIn', {
|
|
1191
|
+
s: retryInSec
|
|
1192
|
+
}) : '')
|
|
1193
|
+
}))),
|
|
1194
|
+
el('div', {
|
|
1195
|
+
className: 'dn-host-actions'
|
|
1196
|
+
},
|
|
1197
|
+
el('button', {
|
|
1198
|
+
className: 'dn-btn primary',
|
|
1199
|
+
onClick: () => void api.retryHost()
|
|
1200
|
+
}, retryInSec > 0 ? t('hostErrRetryNow') : t('hostErrRetry')),
|
|
1201
|
+
el('button', {
|
|
1202
|
+
className: 'dn-btn',
|
|
1203
|
+
onClick: () => setHostDetail(!hostDetail)
|
|
1204
|
+
}, hostDetail ? t('hostErrDetailHide') : t('hostErrDetail'))),
|
|
1205
|
+
hostDetail ? el('div', {
|
|
1206
|
+
className: 'dn-host-detail'
|
|
1207
|
+
}, hostError.signal !== null && hostError.signal !== undefined ? t('hostErrSignal', {
|
|
1208
|
+
code: hostError.code === null ? '-' : hostError.code,
|
|
1209
|
+
signal: hostError.signal,
|
|
1210
|
+
n: hostError.count
|
|
1211
|
+
}) : t('hostErrDetailText', {
|
|
1212
|
+
code: hostError.code === null ? '-' : hostError.code,
|
|
1213
|
+
n: hostError.count
|
|
1214
|
+
})) : null) : null,
|
|
1087
1215
|
msg !== null ? el('div', {
|
|
1088
1216
|
className: 'dn-msg ' + msg.kind
|
|
1089
1217
|
}, msg.text) : null,
|
|
@@ -1111,6 +1239,8 @@ window.__ModuleLoader__.load({
|
|
|
1111
1239
|
const store = makeStore({
|
|
1112
1240
|
config: null,
|
|
1113
1241
|
desktop: false,
|
|
1242
|
+
hostError: null,
|
|
1243
|
+
pendingCount: 0,
|
|
1114
1244
|
events: [],
|
|
1115
1245
|
browsers: []
|
|
1116
1246
|
})
|
|
@@ -1125,6 +1255,8 @@ window.__ModuleLoader__.load({
|
|
|
1125
1255
|
store.set({
|
|
1126
1256
|
config: state.config,
|
|
1127
1257
|
desktop: state.desktop,
|
|
1258
|
+
hostError: state.hostError ?? null,
|
|
1259
|
+
pendingCount: state.pendingCount ?? 0,
|
|
1128
1260
|
events: state.events,
|
|
1129
1261
|
browsers: parsed.browsers
|
|
1130
1262
|
})
|
|
@@ -1162,6 +1294,9 @@ window.__ModuleLoader__.load({
|
|
|
1162
1294
|
store.set({
|
|
1163
1295
|
config: parseConfig(result.value, 'config'),
|
|
1164
1296
|
desktop: state.desktop,
|
|
1297
|
+
// ④ 保留宿主故障状态与积压数,否则改一次配置横幅就被抹掉
|
|
1298
|
+
hostError: state.hostError ?? null,
|
|
1299
|
+
pendingCount: state.pendingCount ?? 0,
|
|
1165
1300
|
events: state.events,
|
|
1166
1301
|
browsers: state.browsers
|
|
1167
1302
|
})
|
|
@@ -1186,6 +1321,17 @@ window.__ModuleLoader__.load({
|
|
|
1186
1321
|
} catch {
|
|
1187
1322
|
/* 静默 */ }
|
|
1188
1323
|
},
|
|
1324
|
+
// ④ 横幅上的「重新拉起宿主」:清退避并立刻重试,然后重新拉一次状态。
|
|
1325
|
+
retryHost: async () => {
|
|
1326
|
+
try {
|
|
1327
|
+
await notify.retryHost()
|
|
1328
|
+
} catch {
|
|
1329
|
+
/* 静默 */ }
|
|
1330
|
+
try {
|
|
1331
|
+
await reload()
|
|
1332
|
+
} catch {
|
|
1333
|
+
/* 静默 */ }
|
|
1334
|
+
},
|
|
1189
1335
|
}
|
|
1190
1336
|
|
|
1191
1337
|
// ===== 现在设置 __localeService 并使用 api =====
|