@wenbin_wb/dsh-bridge 2.11.3 → 2.12.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.en.md +25 -25
  3. package/README.md +25 -25
  4. package/client/client.js +49 -2
  5. package/client/index.js +54 -4
  6. package/lib/bridge-rpc-constants.js +2 -0
  7. package/lib/bridge-rpc.js +33 -2
  8. package/lib/index.js +192 -11
  9. package/lib/platform/conversation-bridge.js +21 -6
  10. package/lib/platform/dsh-storage.js +64 -10
  11. package/package.json +4 -5
  12. package/docs/CODE_REVIEW.md +0 -220
  13. package/docs/banner.jpg +0 -0
  14. package/docs/cloudflare-fixed-domain.md +0 -158
  15. package/docs/custom-tunnel.md +0 -220
  16. package/docs/feishu-usage.md +0 -116
  17. package/docs/qq-usage.md +0 -387
  18. package/docs/release-process.md +0 -45
  19. package/docs/screenshots/admin-lock-screen.jpg +0 -0
  20. package/docs/screenshots/feishu-bot-config.jpg +0 -0
  21. package/docs/screenshots/feishu-chat.jpg +0 -0
  22. package/docs/screenshots/lan-access.jpg +0 -0
  23. package/docs/screenshots/mobile-chat.jpg +0 -0
  24. package/docs/screenshots/mobile-drawer.jpg +0 -0
  25. package/docs/screenshots/mobile-remote-settings.jpg +0 -0
  26. package/docs/screenshots/mobile-settings-im.jpg +0 -0
  27. package/docs/screenshots/mobile-settings-lan.jpg +0 -0
  28. package/docs/screenshots/mobile-settings-security.jpg +0 -0
  29. package/docs/screenshots/mobile-settings-tunnel.jpg +0 -0
  30. package/docs/screenshots/mobile-workspace-picker.jpg +0 -0
  31. package/docs/screenshots/qq-bot-config.jpg +0 -0
  32. package/docs/screenshots/qq-chat.jpg +0 -0
  33. package/docs/screenshots/qq-group.jpg +0 -0
  34. package/docs/screenshots/qr-scan.jpg +0 -0
  35. package/docs/screenshots/remote-auth-login.jpg +0 -0
  36. package/docs/screenshots/remote-web-mobile.jpg +0 -0
  37. package/docs/screenshots/security-auth-config.jpg +0 -0
  38. package/docs/screenshots/telegram-bot-config.jpg +0 -0
  39. package/docs/screenshots/tunnel-access.jpg +0 -0
  40. package/docs/screenshots/wechat-bot-config.jpg +0 -0
  41. package/docs/screenshots/wechat-chat.jpg +0 -0
  42. package/docs/telegram-usage.md +0 -93
  43. package/docs/wechat-usage.md +0 -104
package/CHANGELOG.md CHANGED
@@ -4,6 +4,61 @@
4
4
 
5
5
  ---
6
6
 
7
+ ## [v2.12.0] - 2026-10-03
8
+
9
+ > 本版为**安全加固 + 首次启用引导 + 发布包瘦身**版。依据外部安全审计(针对 v2.10.13)逐条核实后修复;按「不破坏现有用户」原则,**默认监听地址保持 `0.0.0.0` 不变**,改用引导提示降低「开箱敞开」风险。
10
+
11
+ ### 🔐 安全
12
+
13
+ - **敏感文件落盘权限收紧为 0600**:`~/.dsh/dsh-bridge/config.json` 含平台 Bot Token、Cloudflare 隧道 JWT、访问密码哈希,此前以 umask 默认(0644/0664)落盘,**同机其他用户可读**;同目录的 `sessions.json` 早已正确使用 0600,属修一半。
14
+ - `writeConfig` 显式带 `mode: 0o600`(仅对新建文件生效)
15
+ - 新增 `ensureConfigFileMode()`:启动时**幂等收敛存量宽松文件**(历史版本遗留的 0644 会被自动收紧)
16
+ - 非 `ENOENT` 错误出声告警,不再静默放过(`EACCES`/`EIO` 意味着无法确认权限状态)
17
+ - 失败仅告警、不阻断启动(Windows 等平台 `chmod` 语义不同)
18
+ - **微信 `wechat-context-tokens.json` 同样收紧为 0600**:该文件为回复微信所必需的会话凭据,与 `config.json` 同类但此前漏修。
19
+ - **WebSocket 升级补齐 Origin 校验(纵深防御,防 CSWSH)**:此前升级握手只校验认证、完全不看 `Origin`,恶意页面可借用户浏览器已有 Cookie 建立 WebSocket。现于**认证之后**追加白名单校验。
20
+ - **关键兼容性约束**:`Origin` 缺失一律放行——IM 机器人、`ws` 库、隧道转发等非浏览器客户端通常不发 `Origin`,按缺失拒绝会直接打断机器人通道
21
+ - 白名单回调抛异常时 fail-closed(拒绝而非放行)
22
+
23
+ ### 🐞 修复
24
+
25
+ - **IM 会话标题恒为「新会话」(DSH 0.1.7 存储格式变更)**:`/sessions` 列表所有会话标题都显示「新会话」。DSH 0.1.7 把 `session_projcache` 领域由**单文件**改为**per-record 目录**(`storages/session_projcache/sessions/<id>.json`),插件仍读旧的 `storages/session_projcache.json`(已不存在)→ 投影缓存恒为空 → 标题只能回退到内存/事件标题,取不到即落成「新会话」。现同时支持两种布局,并改为**按 id 惰性读取**(消费方只按 sessionId 取值,无需在一次 `/sessions` 中同步读取数百个文件;实测构造耗时 0.37ms,本机 470 个缓存文件无一全量加载)。
26
+ - **与 v2.11.3 修的「移动端顶栏标题」是同一根因的两处表现**:那次只改了前端读取源(宿主 DOM 面包屑),未修 IM 侧数据源,故 IM 症状依旧。**升级 DSH 后请同时回归移动端与 IM 两处标题。**
27
+ - **局域网直连原生 3080 端口时 WebSocket 被误拒**:Origin 白名单原先只给 LAN IP 配置 `proxyPort`,而回环同时配置了 3080/3082。用户从局域网访问 DSH 原生端口时表现为「页面能打开、但实时通道全部静默失效」,极难自查。现 LAN IP 与回环一致同时覆盖两个端口。
28
+ - **`reset-auth` 救急重置的生效时机说明与实际不符**:中英文 README 原称「毫秒级自动清空」,实际实现仅在**插件启动时**检测一次标记(无常驻文件监听器)。文档已改为「下次插件启动时生效」,并说明该设计取舍(避免为救急功能引入常驻监听)。
29
+ - **一键升级 DSH 显示成功但实际仍是旧版本**:两个叠加缺陷。① prefix 错位——`_probeDshUpgrade` 用 `dirname(npm root -g)`(本机为 `…/lib`)作为 `npm install -g --prefix`,而 `npm root -g` 返回的是 prefix 下的 `lib/node_modules`,导致实际安装到 `…/lib/lib/node_modules`(本机留下 508MB 错位副本为证),`dsh` 命令仍加载旧目录;npm 退出码为 0,面板遂显示「升级完成」。现改用 `npm prefix -g` 的真实前缀。② 成功即报喜——此前 npm 命令成功就直接返回 `ok:true`,不再核验正在使用的 `dsh` 是否真的变新。现安装成功后立即用同一 `dsh` 路径核验 `dsh --version`:指定版本号时必须精确匹配,否则返回失败并提示手动命令(带 `installedButNotActive` 标记);`latest` 因无法预知目标版本号而跳过核验。升级成功后同时清理版本检查缓存,避免 UI 继续显示旧版本。
30
+
31
+ ### ✨ 新功能
32
+
33
+ - **首次启用引导**:检测到「监听 `0.0.0.0` 但访问认证未开启」时,面板提示用户开启门禁。判据保守,**已开启认证的老用户与已确认过引导的用户一律不再提示**;严格判等(`!== true`),脏数据不误判为已开启。新增 `firstRunGuide` 状态与 `dismissFirstRunGuide` 端点。
34
+ - **重启打断保护**:面板点「重启 DSH」时,若检测到有任务正在运行,**先弹二次确认并列出具体现场**,确认后才真正重启,避免静默打断正在跑的任务。检测三类信号:
35
+ - 进行中的会话(事件流里未闭合的 `turn/start`,与 IM「处理中」同一判据)
36
+ - 进行中的子代理/agent 任务(`origin: 'subagent'` 的嵌套会话,最容易被遗忘、损失也最大)
37
+ - 待审批请求(各 IM 平台尚未被回复的审批,重启会丢失该状态)
38
+ 设计取舍:仅提示 + 二次确认,**不做硬阻断**——重启是救急能力(实例卡死时仍需能重启);且探测逻辑任何一步失败都降级为「无任务」,绝不因探测失败把重启锁死。
39
+
40
+ ### ⚡ 优化
41
+
42
+ - **发布包瘦身 88%**:npm 包长期携带 7.84MB 文档截图(占解包体积 52%),而它们无任何运行时引用(`grep` 确认 `lib/`、`client/` 均不读 `docs/`)。
43
+ - 24 张配图 JPG → WebP(quality 80,超宽图降采样至 1600px):7.77MB → 1.59MB
44
+ - `files` 白名单移除 `"docs"`;README 48 处引用改指 GitHub raw 绝对地址
45
+ - 实测:压缩后 **7.63MB → 0.87MB**、解包 15.04MB → 7.21MB、文件数 89 → 57
46
+ - 剩余体积下限为 `lib/feishu/lark-bundled.mjs`(5.87MB 飞书 SDK),属功能必需,未在本次处理
47
+
48
+ ### 🧪 测试
49
+
50
+ - 新增 41 项回归用例:`test/security-hardening.test.mjs`(6 项 Origin 分支:无 Origin 放行、白名单放行、陌生 Origin 403、`Origin: null` 403、认证优先 401、回调抛异常 fail-closed)、`test/config-file-permissions.test.mjs`(6 项权限契约:新建带 mode、存量收敛且内容不变、幂等、ENOENT 静默、错误分类、失败不抛)、`test/first-run-guide.test.mjs`(6 项引导判据:新用户提示、已开认证不提示、已确认不提示、老用户升级不打扰、设密码但未开启仍提示、脏数据不误判)、`test/session-projcache-layout.test.mjs`(8 项投影缓存布局:0.1.7 新布局、0.1.6 旧布局、并存优先级、双缺失、未知 id、损坏文件、内存注入优先、惰性读取)、`test/restart-active-work-guard.test.mjs`(10 项重启打断保护:未闭合 turn 判定、已结束会话不计入、子代理归类、待审批计入、无任务不确认,以及「探测失败不得阻断重启」的 5 项护栏)、`test/dsh-upgrade.test.mjs`(5 项一键升级 DSH:--prefix 采用 npm prefix -g、指定版本安装后核验 dsh --version、预发布标签放行 + 注入拦截、成功后清版本缓存、latest 跳过核验)。全量 **384 项测试 100% 通过**,CI 覆盖 ubuntu/windows × Node 22/24,CodeQL 无告警。
51
+ - 修正既有用例对文档资源的耦合:`media-and-heartbeat.test.mjs` 原以 `docs/banner.jpg` 为夹具,改用 `package.json`。
52
+
53
+ ### 📝 升级说明
54
+
55
+ - **无破坏性变更**:默认监听地址、端口、`exports` 与配置默认值均未改动,现有部署不受影响。
56
+ - 权限收紧与 Origin 校验在 **dsh 重启后**生效;存量 `config.json` 会在首次启动时被自动收敛为 0600。
57
+ - **隧道数据面已实测验证**(针对本版发布前,用真实 Cloudflare 隧道 + 公网域名):公网访客被门禁拦下(401 + 登录页);伪造 `Host: 127.0.0.1` 被 403 拒绝(防 DNS rebinding);未认证 WebSocket 升级被 401 拒绝;二维码 Token 免密直达(302 → `HttpOnly` + `SameSite=Lax` 会话 Cookie)后可换取真实访问(200);隧道 QUIC 断连自动重连、无需人工干预。
58
+ - **仍未验证**:四条 IM 通道(微信/QQ/飞书/Telegram)的**真实平台端到端对话**、PWA 移动端渲染、多网卡切换。这些结论不适用于本版。
59
+
60
+ ---
61
+
7
62
  ## [v2.11.3] - 2026-10-02
8
63
 
9
64
  > 本版为移动端修复 + 国际化版:修复顶栏会话标题恒为「新会话」,并让插件 UI 与显示名称跟随 DSH 主体语言(中/英)。
package/README.en.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # dsh-bridge
2
2
 
3
3
  <p align="center">
4
- <img src="docs/banner.jpg" alt="dsh-bridge banner" width="100%" />
4
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/banner.webp" alt="dsh-bridge banner" width="100%" />
5
5
  </p>
6
6
 
7
7
  <p align="center">
@@ -128,7 +128,7 @@ Launch DeepSeek Harness, open Settings in the left sidebar, and click **"Remote
128
128
  Starts **automatically with DSH service**, zero configuration required.
129
129
 
130
130
  <p align="center">
131
- <img src="docs/screenshots/lan-access.jpg" width="600" alt="LAN Access Console" />
131
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/lan-access.webp" width="600" alt="LAN Access Console" />
132
132
  </p>
133
133
 
134
134
  * **Instant QR Code Scan**: Connect phone and PC to the same Wi-Fi, scan the QR code with phone camera to access mobile web UI;
@@ -141,7 +141,7 @@ Starts **automatically with DSH service**, zero configuration required.
141
141
  Access DeepSeek Harness from anywhere outside your home network without public IP or router port forwarding:
142
142
 
143
143
  <p align="center">
144
- <img src="docs/screenshots/tunnel-access.jpg" width="600" alt="Tunnel Access Configuration" />
144
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/tunnel-access.webp" width="600" alt="Tunnel Access Configuration" />
145
145
  </p>
146
146
 
147
147
  - **Mode 1: Zero-Login Temporary Tunnel (Default)**:
@@ -169,25 +169,25 @@ Deeply optimized for mobile screens and touch interactions:
169
169
  #### Mobile Chat & Workspace Experience
170
170
 
171
171
  <p align="center">
172
- <img src="docs/screenshots/remote-web-mobile.jpg" width="23%" alt="Mobile Home" />
172
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/remote-web-mobile.webp" width="23%" alt="Mobile Home" />
173
173
  &nbsp;
174
- <img src="docs/screenshots/mobile-chat.jpg" width="23%" alt="Mobile Chat" />
174
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/mobile-chat.webp" width="23%" alt="Mobile Chat" />
175
175
  &nbsp;
176
- <img src="docs/screenshots/mobile-drawer.jpg" width="23%" alt="Mobile Drawer" />
176
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/mobile-drawer.webp" width="23%" alt="Mobile Drawer" />
177
177
  &nbsp;
178
- <img src="docs/screenshots/mobile-workspace-picker.jpg" width="23%" alt="Mobile Workspace Picker" />
178
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/mobile-workspace-picker.webp" width="23%" alt="Mobile Workspace Picker" />
179
179
  </p>
180
180
 
181
181
  #### Remote Settings Center on Mobile
182
182
 
183
183
  <p align="center">
184
- <img src="docs/screenshots/mobile-settings-lan.jpg" width="23%" alt="LAN Settings" />
184
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/mobile-settings-lan.webp" width="23%" alt="LAN Settings" />
185
185
  &nbsp;
186
- <img src="docs/screenshots/mobile-settings-tunnel.jpg" width="23%" alt="Tunnel Settings" />
186
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/mobile-settings-tunnel.webp" width="23%" alt="Tunnel Settings" />
187
187
  &nbsp;
188
- <img src="docs/screenshots/mobile-settings-im.jpg" width="23%" alt="IM Bot Settings" />
188
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/mobile-settings-im.webp" width="23%" alt="IM Bot Settings" />
189
189
  &nbsp;
190
- <img src="docs/screenshots/mobile-settings-security.jpg" width="23%" alt="Security Settings" />
190
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/mobile-settings-security.webp" width="23%" alt="Security Settings" />
191
191
  </p>
192
192
 
193
193
  ---
@@ -197,7 +197,7 @@ Deeply optimized for mobile screens and touch interactions:
197
197
  Solves the pain point of mobile browsers being unable to trigger PC native folder dialogs:
198
198
 
199
199
  <p align="center">
200
- <img src="docs/screenshots/mobile-workspace-picker.jpg" width="380" alt="Mobile Workspace Picker" />
200
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/mobile-workspace-picker.webp" width="380" alt="Mobile Workspace Picker" />
201
201
  </p>
202
202
 
203
203
  * **Smart Routing**: PC localhost visits (`127.0.0.1`) invoke OS native file dialogs; mobile/remote visits pop up responsive bottom directory browser;
@@ -210,7 +210,7 @@ Solves the pain point of mobile browsers being unable to trigger PC native folde
210
210
  Open **"Security"** tab to establish bank-grade protection for your local development environment:
211
211
 
212
212
  <p align="center">
213
- <img src="docs/screenshots/security-auth-config.jpg" width="600" alt="Security Config" />
213
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/security-auth-config.webp" width="600" alt="Security Config" />
214
214
  </p>
215
215
 
216
216
  #### 1. 🛡️ Line 1: External Access Gateway
@@ -221,7 +221,7 @@ Open **"Security"** tab to establish bank-grade protection for your local develo
221
221
  <summary>📱 Click to view Remote Access Login Page</summary>
222
222
  <br/>
223
223
  <p align="center">
224
- <img src="docs/screenshots/remote-auth-login.jpg" width="500" alt="Remote Access Login Page" />
224
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/remote-auth-login.webp" width="500" alt="Remote Access Login Page" />
225
225
  </p>
226
226
  </details>
227
227
 
@@ -233,13 +233,13 @@ Open **"Security"** tab to establish bank-grade protection for your local develo
233
233
  <summary>🖥️ Click to view Admin Console Lock Screen</summary>
234
234
  <br/>
235
235
  <p align="center">
236
- <img src="docs/screenshots/admin-lock-screen.jpg" width="500" alt="Admin Console Lock Screen" />
236
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/admin-lock-screen.webp" width="500" alt="Admin Console Lock Screen" />
237
237
  </p>
238
238
  </details>
239
239
 
240
240
  #### 3. 🛟 Triple Disaster Recovery (Never Locked Out)
241
241
  - **Host Physical Privilege**: PC localhost (`127.0.0.1`) enjoys permanent highest privilege, never locked;
242
- - **Terminal Emergency Reset**: Run `touch ~/.dsh/dsh-bridge/reset-auth` in terminal to reset passwords instantly;
242
+ - **Terminal Emergency Reset**: Run `touch ~/.dsh/dsh-bridge/reset-auth` in terminal to reset passwords and security policy (takes effect on the next plugin start / DSH restart);
243
243
  - **Interactive Guidance**: Built-in interactive recovery guides on all auth pages.
244
244
 
245
245
  ---
@@ -255,14 +255,14 @@ Interact with local AI agents directly inside your favorite messaging apps witho
255
255
  Scan QR code with personal WeChat account to chat, manage sessions, and approve permissions via official Tencent servers without public IP.
256
256
 
257
257
  <p align="center">
258
- <img src="docs/screenshots/wechat-bot-config.jpg" width="600" alt="WeChat Bot Config" />
258
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/wechat-bot-config.webp" width="600" alt="WeChat Bot Config" />
259
259
  </p>
260
260
 
261
261
  <details>
262
262
  <summary>📱 Click to view WeChat Chat & Approval</summary>
263
263
  <br/>
264
264
  <p align="center">
265
- <img src="docs/screenshots/wechat-chat.jpg" width="380" alt="WeChat Chat" />
265
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/wechat-chat.webp" width="380" alt="WeChat Chat" />
266
266
  </p>
267
267
  </details>
268
268
 
@@ -275,15 +275,15 @@ Scan QR code with personal WeChat account to chat, manage sessions, and approve
275
275
  Official QQ Bot with direct/group @chat, Markdown rendering, interactive button keyboards, and rich media transfers.
276
276
 
277
277
  <p align="center">
278
- <img src="docs/screenshots/qq-bot-config.jpg" width="600" alt="QQ Bot Config" />
278
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/qq-bot-config.webp" width="600" alt="QQ Bot Config" />
279
279
  </p>
280
280
 
281
281
  <details>
282
282
  <summary>📱 Click to view QQ Direct & Group Chat</summary>
283
283
  <br/>
284
284
  <p align="center">
285
- <img src="docs/screenshots/qq-chat.jpg" width="48%" alt="QQ Direct Chat" />
286
- <img src="docs/screenshots/qq-group.jpg" width="48%" alt="QQ Group Chat" />
285
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/qq-chat.webp" width="48%" alt="QQ Direct Chat" />
286
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/qq-group.webp" width="48%" alt="QQ Group Chat" />
287
287
  </p>
288
288
  </details>
289
289
 
@@ -296,14 +296,14 @@ Official QQ Bot with direct/group @chat, Markdown rendering, interactive button
296
296
  Enterprise self-built app via official full-duplex WebSocket long connection—**100% No Public IP / No Webhook required**.
297
297
 
298
298
  <p align="center">
299
- <img src="docs/screenshots/feishu-bot-config.jpg" width="600" alt="Feishu Bot Config" />
299
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/feishu-bot-config.webp" width="600" alt="Feishu Bot Config" />
300
300
  </p>
301
301
 
302
302
  <details>
303
303
  <summary>📱 Click to view Feishu Chat & Card Approval</summary>
304
304
  <br/>
305
305
  <p align="center">
306
- <img src="docs/screenshots/feishu-chat.jpg" width="380" alt="Feishu Chat" />
306
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/feishu-chat.webp" width="380" alt="Feishu Chat" />
307
307
  </p>
308
308
  </details>
309
309
 
@@ -316,7 +316,7 @@ Enterprise self-built app via official full-duplex WebSocket long connection—*
316
316
  Official Telegram Bot API with Long Polling and **built-in zero-dependency HTTP/HTTPS proxy tunnel**.
317
317
 
318
318
  <p align="center">
319
- <img src="docs/screenshots/telegram-bot-config.jpg" width="600" alt="Telegram Bot Config" />
319
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/telegram-bot-config.webp" width="600" alt="Telegram Bot Config" />
320
320
  </p>
321
321
 
322
322
  * **Quick Setup**: Create bot with [@BotFather](https://t.me/BotFather) > Fill Bot Token (and optional proxy) > Send first message to auto-authorize. See [Telegram Guide](docs/telegram-usage.md).
@@ -348,7 +348,7 @@ Official Telegram Bot API with Long Polling and **built-in zero-dependency HTTP/
348
348
  Open **"Maintenance"** tab to monitor health and manage operations:
349
349
 
350
350
  <p align="center">
351
- <img src="docs/screenshots/mobile-remote-settings.jpg" width="380" alt="Maintenance Dashboard" />
351
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/mobile-remote-settings.webp" width="380" alt="Maintenance Dashboard" />
352
352
  </p>
353
353
 
354
354
  * **📊 Host System Metrics Dashboard**: Real-time CPU model, total/used RAM, Node heap memory, and DSH uptime;
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # dsh-bridge
2
2
 
3
3
  <p align="center">
4
- <img src="docs/banner.jpg" alt="dsh-bridge banner" width="100%" />
4
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/banner.webp" alt="dsh-bridge banner" width="100%" />
5
5
  </p>
6
6
 
7
7
  <p align="center">
@@ -132,7 +132,7 @@ dsh plugin --profile web add @wenbin_wb/dsh-bridge@latest
132
132
  插件启动后**自动随服务开启**局域网代理,无需手动配置。
133
133
 
134
134
  <p align="center">
135
- <img src="docs/screenshots/lan-access.jpg" width="600" alt="局域网扫码访问控制台" />
135
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/lan-access.webp" width="600" alt="局域网扫码访问控制台" />
136
136
  </p>
137
137
 
138
138
  * **零配置极速扫码**:同一 Wi-Fi 下打开手机相机扫码即可直达移动端 Web 界面;
@@ -145,7 +145,7 @@ dsh plugin --profile web add @wenbin_wb/dsh-bridge@latest
145
145
  无需公网 IP 与路由器端口映射,随时随地从外网访问电脑上的 DeepSeek Harness:
146
146
 
147
147
  <p align="center">
148
- <img src="docs/screenshots/tunnel-access.jpg" width="600" alt="公网隧道配置控制台" />
148
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/tunnel-access.webp" width="600" alt="公网隧道配置控制台" />
149
149
  </p>
150
150
 
151
151
  - **模式 1:极速免登录临时隧道(默认)**
@@ -177,25 +177,25 @@ dsh plugin --profile web add @wenbin_wb/dsh-bridge@latest
177
177
  #### 移动端对话与工作区管理体验
178
178
 
179
179
  <p align="center">
180
- <img src="docs/screenshots/remote-web-mobile.jpg" width="23%" alt="移动端新会话主页" />
180
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/remote-web-mobile.webp" width="23%" alt="移动端新会话主页" />
181
181
  &nbsp;
182
- <img src="docs/screenshots/mobile-chat.jpg" width="23%" alt="移动端已有对话交互" />
182
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/mobile-chat.webp" width="23%" alt="移动端已有对话交互" />
183
183
  &nbsp;
184
- <img src="docs/screenshots/mobile-drawer.jpg" width="23%" alt="移动端原生抽屉侧边栏" />
184
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/mobile-drawer.webp" width="23%" alt="移动端原生抽屉侧边栏" />
185
185
  &nbsp;
186
- <img src="docs/screenshots/mobile-workspace-picker.jpg" width="23%" alt="移动端远程工作区选择器" />
186
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/mobile-workspace-picker.webp" width="23%" alt="移动端远程工作区选择器" />
187
187
  </p>
188
188
 
189
189
  #### 远程访问与移动端设置中心
190
190
 
191
191
  <p align="center">
192
- <img src="docs/screenshots/mobile-settings-lan.jpg" width="23%" alt="局域网访问控制台" />
192
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/mobile-settings-lan.webp" width="23%" alt="局域网访问控制台" />
193
193
  &nbsp;
194
- <img src="docs/screenshots/mobile-settings-tunnel.jpg" width="23%" alt="公网隧道配置" />
194
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/mobile-settings-tunnel.webp" width="23%" alt="公网隧道配置" />
195
195
  &nbsp;
196
- <img src="docs/screenshots/mobile-settings-im.jpg" width="23%" alt="IM 机器人矩阵" />
196
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/mobile-settings-im.webp" width="23%" alt="IM 机器人矩阵" />
197
197
  &nbsp;
198
- <img src="docs/screenshots/mobile-settings-security.jpg" width="23%" alt="全局访问安全认证" />
198
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/mobile-settings-security.webp" width="23%" alt="全局访问安全认证" />
199
199
  </p>
200
200
 
201
201
  ---
@@ -205,7 +205,7 @@ dsh plugin --profile web add @wenbin_wb/dsh-bridge@latest
205
205
  针对手机端或远程浏览器无法唤起本地电脑文件弹窗的痛点,内置响应式网页树形目录浏览器:
206
206
 
207
207
  <p align="center">
208
- <img src="docs/screenshots/mobile-workspace-picker.jpg" width="380" alt="移动端远程工作区网页选择器" />
208
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/mobile-workspace-picker.webp" width="380" alt="移动端远程工作区网页选择器" />
209
209
  </p>
210
210
 
211
211
  * **智能分流**:电脑本机访问(`127.0.0.1`)点击添加工作区直接呼出系统原生文件弹窗;手机或远程访问时自动弹出响应式底部目录抽屉;
@@ -218,7 +218,7 @@ dsh plugin --profile web add @wenbin_wb/dsh-bridge@latest
218
218
  打开设置页「远程访问」→「**安全认证**」Tab 即可一键启用全方位安全守护:
219
219
 
220
220
  <p align="center">
221
- <img src="docs/screenshots/security-auth-config.jpg" width="600" alt="安全认证配置面板" />
221
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/security-auth-config.webp" width="600" alt="安全认证配置面板" />
222
222
  </p>
223
223
 
224
224
  #### 1. 🛡️ 第一道防线:外部访问门禁(保护谁能进 Web 界面)
@@ -236,7 +236,7 @@ dsh plugin --profile web add @wenbin_wb/dsh-bridge@latest
236
236
  <summary>📱 点击展开外部访问安全认证登录页截图</summary>
237
237
  <br/>
238
238
  <p align="center">
239
- <img src="docs/screenshots/remote-auth-login.jpg" width="500" alt="外部访问安全认证登录页" />
239
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/remote-auth-login.webp" width="500" alt="外部访问安全认证登录页" />
240
240
  </p>
241
241
  </details>
242
242
 
@@ -251,7 +251,7 @@ dsh plugin --profile web add @wenbin_wb/dsh-bridge@latest
251
251
  <summary>🖥️ 点击展开远程设备管理控制台防篡改锁定截图</summary>
252
252
  <br/>
253
253
  <p align="center">
254
- <img src="docs/screenshots/admin-lock-screen.jpg" width="500" alt="管理控制台防篡改锁定" />
254
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/admin-lock-screen.webp" width="500" alt="管理控制台防篡改锁定" />
255
255
  </p>
256
256
  </details>
257
257
 
@@ -261,7 +261,7 @@ dsh plugin --profile web add @wenbin_wb/dsh-bridge@latest
261
261
  ```bash
262
262
  touch ~/.dsh/dsh-bridge/reset-auth
263
263
  ```
264
- 插件将在毫秒级自动清空密码与策略并删除标记,瞬间恢复初始免密状态;
264
+ 插件将在**下次插件启动时**检测到该标记,自动清空密码与安全策略并删除标记,恢复初始免密状态(重启 DSH 或重载插件后生效);
265
265
  - **全界面忘记密码指引**:访客登录页与锁屏页均提供 `❓ 忘记密码?` 救助展开卡片。
266
266
 
267
267
  ---
@@ -277,14 +277,14 @@ dsh plugin --profile web add @wenbin_wb/dsh-bridge@latest
277
277
  基于腾讯官方开放的微信 ClawBot 插件功能(底层 iLink Bot API),扫码登录微信个人号后即可对话与审批,全程走腾讯官方服务器,无需公网与隧道。
278
278
 
279
279
  <p align="center">
280
- <img src="docs/screenshots/wechat-bot-config.jpg" width="600" alt="微信 Bot 配置" />
280
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/wechat-bot-config.webp" width="600" alt="微信 Bot 配置" />
281
281
  </p>
282
282
 
283
283
  <details>
284
284
  <summary>📱 点击展开手机微信对话与审批截图</summary>
285
285
  <br/>
286
286
  <p align="center">
287
- <img src="docs/screenshots/wechat-chat.jpg" width="380" alt="微信对话示例" />
287
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/wechat-chat.webp" width="380" alt="微信对话示例" />
288
288
  </p>
289
289
  </details>
290
290
 
@@ -297,15 +297,15 @@ dsh plugin --profile web add @wenbin_wb/dsh-bridge@latest
297
297
  接入 QQ 官方机器人,支持单聊与群聊(群聊需 @机器人),支持 Markdown 渲染、快捷按钮键盘与富媒体文件直传。
298
298
 
299
299
  <p align="center">
300
- <img src="docs/screenshots/qq-bot-config.jpg" width="600" alt="QQ Bot 配置" />
300
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/qq-bot-config.webp" width="600" alt="QQ Bot 配置" />
301
301
  </p>
302
302
 
303
303
  <details>
304
304
  <summary>📱 点击展开手机 QQ 单聊与群聊对话截图</summary>
305
305
  <br/>
306
306
  <p align="center">
307
- <img src="docs/screenshots/qq-chat.jpg" width="48%" alt="QQ 单聊对话" />
308
- <img src="docs/screenshots/qq-group.jpg" width="48%" alt="QQ 群聊对话" />
307
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/qq-chat.webp" width="48%" alt="QQ 单聊对话" />
308
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/qq-group.webp" width="48%" alt="QQ 群聊对话" />
309
309
  </p>
310
310
  </details>
311
311
 
@@ -318,14 +318,14 @@ dsh plugin --profile web add @wenbin_wb/dsh-bridge@latest
318
318
  接入飞书开放平台企业自建应用,采用官方 WebSocket 全双工长连接,**100% 免公网 IP / 免域名 / 免配置 Webhook**。
319
319
 
320
320
  <p align="center">
321
- <img src="docs/screenshots/feishu-bot-config.jpg" width="600" alt="飞书 Bot 配置" />
321
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/feishu-bot-config.webp" width="600" alt="飞书 Bot 配置" />
322
322
  </p>
323
323
 
324
324
  <details>
325
325
  <summary>📱 点击展开手机飞书对话与卡片审批截图</summary>
326
326
  <br/>
327
327
  <p align="center">
328
- <img src="docs/screenshots/feishu-chat.jpg" width="380" alt="飞书对话与卡片审批示例" />
328
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/feishu-chat.webp" width="380" alt="飞书对话与卡片审批示例" />
329
329
  </p>
330
330
  </details>
331
331
 
@@ -338,7 +338,7 @@ dsh plugin --profile web add @wenbin_wb/dsh-bridge@latest
338
338
  接入 Telegram 官方 Bot API,单聊/群聊实时交互。采用官方 Long Polling(长轮询)机制,内置**零依赖 HTTP/HTTPS CONNECT 代理隧道**。
339
339
 
340
340
  <p align="center">
341
- <img src="docs/screenshots/telegram-bot-config.jpg" width="600" alt="Telegram Bot 配置" />
341
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/telegram-bot-config.webp" width="600" alt="Telegram Bot 配置" />
342
342
  </p>
343
343
 
344
344
  * **使用步骤**:向 [@BotFather](https://t.me/BotFather) 发送 `/newbot` 创建机器人获取 **Bot Token** → 填入 Token(国内可填代理如 `http://127.0.0.1:7890`)并保存 → 发送第一条消息自动完成授权。完整文档见 [Telegram 使用说明](docs/telegram-usage.md)。
@@ -370,7 +370,7 @@ dsh plugin --profile web add @wenbin_wb/dsh-bridge@latest
370
370
  打开控制台 **「运维监控」** Tab,实时掌控系统状态与一键维护:
371
371
 
372
372
  <p align="center">
373
- <img src="docs/screenshots/mobile-remote-settings.jpg" width="380" alt="运维监控看板与系统状态" />
373
+ <img src="https://raw.githubusercontent.com/wenbin-wb/dsh-bridge/main/docs/screenshots/mobile-remote-settings.webp" width="380" alt="运维监控看板与系统状态" />
374
374
  </p>
375
375
 
376
376
  * **📊 宿主系统运行监控看板**:实时展示 CPU 核心型号、系统总内存与实时占用率、Node 进程堆内存与 DSH 服务连续运行时间(Uptime);
package/client/client.js CHANGED
@@ -1742,6 +1742,8 @@ var BRIDGE_ENDPOINTS = {
1742
1742
  authRegenerateToken: "authRegenerateToken",
1743
1743
  authAdminUnlock: "authAdminUnlock",
1744
1744
  authAdminLock: "authAdminLock",
1745
+ // 首次启用引导:用户确认已了解后置位,避免重复打扰
1746
+ dismissFirstRunGuide: "dismissFirstRunGuide",
1745
1747
  // 平台管理器(多 IM 平台统一接口)
1746
1748
  listPlatforms: "listPlatforms",
1747
1749
  platformLogin: "platformLogin",
@@ -4423,6 +4425,38 @@ function BackupRestoreWidget({ rpcCall, onUpdate }) {
4423
4425
  }, msg.text)
4424
4426
  );
4425
4427
  }
4428
+ function formatActiveWorkWarning(work) {
4429
+ if (!work || !work.total) return null;
4430
+ const lines = [];
4431
+ if (work.sessions?.length) {
4432
+ lines.push(`\u2022 \u8FDB\u884C\u4E2D\u7684\u4F1A\u8BDD ${work.sessions.length} \u4E2A\uFF1A`);
4433
+ for (const s2 of work.sessions.slice(0, 5)) {
4434
+ const detail = s2.tools > 0 ? `${s2.tools} \u6B21\u5DE5\u5177\u8C03\u7528${s2.lastTool ? `\uFF08\u6700\u8FD1\uFF1A${s2.lastTool}\uFF09` : ""}` : "\u601D\u8003\u4E2D";
4435
+ lines.push(` - ${s2.title}\uFF08\u7B2C ${s2.turn} \u8F6E\uFF0C${detail}\uFF09`);
4436
+ }
4437
+ if (work.sessions.length > 5) lines.push(` \u2026\u7B49\u5171 ${work.sessions.length} \u4E2A`);
4438
+ }
4439
+ if (work.subagentSessions?.length) {
4440
+ lines.push(`\u2022 \u8FDB\u884C\u4E2D\u7684\u5B50\u4EE3\u7406/agent \u4EFB\u52A1 ${work.subagentSessions.length} \u4E2A\uFF1A`);
4441
+ for (const s2 of work.subagentSessions.slice(0, 5)) {
4442
+ const detail = s2.tools > 0 ? `${s2.tools} \u6B21\u5DE5\u5177\u8C03\u7528` : "\u601D\u8003\u4E2D";
4443
+ lines.push(` - ${s2.title}\uFF08\u7B2C ${s2.turn} \u8F6E\uFF0C${detail}\uFF09`);
4444
+ }
4445
+ if (work.subagentSessions.length > 5) lines.push(` \u2026\u7B49\u5171 ${work.subagentSessions.length} \u4E2A`);
4446
+ }
4447
+ if (work.pendingApprovals?.length) {
4448
+ lines.push(`\u2022 \u5F85\u5BA1\u6279\u8BF7\u6C42 ${work.pendingApprovals.length} \u4E2A\uFF1A`);
4449
+ for (const a of work.pendingApprovals.slice(0, 5)) {
4450
+ lines.push(` - [${a.platform}] ${a.summary}`);
4451
+ }
4452
+ if (work.pendingApprovals.length > 5) lines.push(` \u2026\u7B49\u5171 ${work.pendingApprovals.length} \u4E2A`);
4453
+ }
4454
+ return `\u26A0\uFE0F \u68C0\u6D4B\u5230\u6709\u4EFB\u52A1\u6B63\u5728\u8FD0\u884C\uFF0C\u91CD\u542F\u4F1A\u4E2D\u65AD\u5B83\u4EEC\uFF1A
4455
+
4456
+ ${lines.join("\n")}
4457
+
4458
+ \u4E2D\u65AD\u540E\u53EF\u80FD\u9700\u8981\u91CD\u65B0\u53D1\u8D77\u3002\u786E\u5B9A\u8981\u7EE7\u7EED\u91CD\u542F\u5417\uFF1F`;
4459
+ }
4426
4460
  function useDshRestart({ rpcCall, maxAttempts = 30, texts = {} } = {}) {
4427
4461
  const T = {
4428
4462
  restarting: "\u6B63\u5728\u5411 DSH \u670D\u52A1\u53D1\u9001\u91CD\u542F\u6307\u4EE4\u2026",
@@ -4444,8 +4478,21 @@ function useDshRestart({ rpcCall, maxAttempts = 30, texts = {} } = {}) {
4444
4478
  setStatus({ phase: "restarting", text: T.restarting });
4445
4479
  try {
4446
4480
  const r = await rpcCall(BRIDGE_ENDPOINTS.restartDsh, {});
4447
- if (r && r.ok === false) {
4448
- setStatus({ phase: "timeout", text: `${T.timeout}\uFF1A${r.error || "\u672A\u77E5\u539F\u56E0"}` });
4481
+ if (r && r.ok === false && r.error?.code === "busy") {
4482
+ const warning = formatActiveWorkWarning(r.error?.details?.activeWork);
4483
+ if (!window.confirm(warning || "\u5F53\u524D\u6709\u4EFB\u52A1\u6B63\u5728\u8FD0\u884C\uFF0C\u91CD\u542F\u4F1A\u4E2D\u65AD\u5B83\u4EEC\u3002\u786E\u5B9A\u7EE7\u7EED\u5417\uFF1F")) {
4484
+ setStatus(null);
4485
+ setRestarting(false);
4486
+ return;
4487
+ }
4488
+ const r2 = await rpcCall(BRIDGE_ENDPOINTS.restartDsh, { confirm: true });
4489
+ if (r2 && r2.ok === false) {
4490
+ setStatus({ phase: "timeout", text: `${T.timeout}\uFF1A${r2.error?.message || "\u672A\u77E5\u539F\u56E0"}` });
4491
+ setRestarting(false);
4492
+ return;
4493
+ }
4494
+ } else if (r && r.ok === false) {
4495
+ setStatus({ phase: "timeout", text: `${T.timeout}\uFF1A${r.error?.message || r.error || "\u672A\u77E5\u539F\u56E0"}` });
4449
4496
  setRestarting(false);
4450
4497
  return;
4451
4498
  }
package/client/index.js CHANGED
@@ -2348,6 +2348,41 @@ function BackupRestoreWidget({ rpcCall, onUpdate }) {
2348
2348
  // 重启 + 健康轮询公共逻辑:定时器保存在 ref 中,组件卸载即清理。
2349
2349
  // 修复点:原先两处复制粘贴的 setInterval 不随组件卸载清理,用户切走 Tab 后
2350
2350
  // 轮询仍会继续运行,并在服务恢复时无条件执行 window.location.reload()。
2351
+
2352
+ /**
2353
+ * 把「正在运行的任务」格式化成二次确认文案。
2354
+ * 返回 null 表示没有任务在跑,调用方无需确认。
2355
+ * @param {{sessions?:Array, subagentSessions?:Array, pendingApprovals?:Array, total?:number}} work
2356
+ */
2357
+ function formatActiveWorkWarning(work) {
2358
+ if (!work || !work.total) return null;
2359
+ const lines = [];
2360
+ if (work.sessions?.length) {
2361
+ lines.push(`• 进行中的会话 ${work.sessions.length} 个:`);
2362
+ for (const s of work.sessions.slice(0, 5)) {
2363
+ const detail = s.tools > 0 ? `${s.tools} 次工具调用${s.lastTool ? `(最近:${s.lastTool})` : ''}` : '思考中';
2364
+ lines.push(` - ${s.title}(第 ${s.turn} 轮,${detail})`);
2365
+ }
2366
+ if (work.sessions.length > 5) lines.push(` …等共 ${work.sessions.length} 个`);
2367
+ }
2368
+ if (work.subagentSessions?.length) {
2369
+ lines.push(`• 进行中的子代理/agent 任务 ${work.subagentSessions.length} 个:`);
2370
+ for (const s of work.subagentSessions.slice(0, 5)) {
2371
+ const detail = s.tools > 0 ? `${s.tools} 次工具调用` : '思考中';
2372
+ lines.push(` - ${s.title}(第 ${s.turn} 轮,${detail})`);
2373
+ }
2374
+ if (work.subagentSessions.length > 5) lines.push(` …等共 ${work.subagentSessions.length} 个`);
2375
+ }
2376
+ if (work.pendingApprovals?.length) {
2377
+ lines.push(`• 待审批请求 ${work.pendingApprovals.length} 个:`);
2378
+ for (const a of work.pendingApprovals.slice(0, 5)) {
2379
+ lines.push(` - [${a.platform}] ${a.summary}`);
2380
+ }
2381
+ if (work.pendingApprovals.length > 5) lines.push(` …等共 ${work.pendingApprovals.length} 个`);
2382
+ }
2383
+ return `⚠️ 检测到有任务正在运行,重启会中断它们:\n\n${lines.join('\n')}\n\n中断后可能需要重新发起。确定要继续重启吗?`;
2384
+ }
2385
+
2351
2386
  function useDshRestart({ rpcCall, maxAttempts = 30, texts = {} } = {}) {
2352
2387
  const T = {
2353
2388
  restarting: '正在向 DSH 服务发送重启指令…',
@@ -2371,10 +2406,25 @@ function useDshRestart({ rpcCall, maxAttempts = 30, texts = {} } = {}) {
2371
2406
  setStatus({ phase: 'restarting', text: T.restarting });
2372
2407
  try {
2373
2408
  const r = await rpcCall(BRIDGE_ENDPOINTS.restartDsh, {});
2374
- // 宿主已明确告知"没能安排重启"(例如 systemd 调用失败、助手都派生不出来):
2375
- // 这时绝不能再假装"正在重连"——直接报错并停下,否则用户只会看到永远转圈。
2376
- if (r && r.ok === false) {
2377
- setStatus({ phase: 'timeout', text: `${T.timeout}:${r.error || '未知原因'}` });
2409
+ // 有任务在跑:宿主不会重启,而是把现场回给面板,由这里弹二次确认。
2410
+ // 确认后带 confirm:true 再请求一次(宿主才真正执行重启)。
2411
+ if (r && r.ok === false && r.error?.code === 'busy') {
2412
+ const warning = formatActiveWorkWarning(r.error?.details?.activeWork);
2413
+ if (!window.confirm(warning || '当前有任务正在运行,重启会中断它们。确定继续吗?')) {
2414
+ setStatus(null);
2415
+ setRestarting(false);
2416
+ return;
2417
+ }
2418
+ const r2 = await rpcCall(BRIDGE_ENDPOINTS.restartDsh, { confirm: true });
2419
+ if (r2 && r2.ok === false) {
2420
+ setStatus({ phase: 'timeout', text: `${T.timeout}:${r2.error?.message || '未知原因'}` });
2421
+ setRestarting(false);
2422
+ return;
2423
+ }
2424
+ } else if (r && r.ok === false) {
2425
+ // 宿主已明确告知"没能安排重启"(例如 systemd 调用失败、助手都派生不出来):
2426
+ // 这时绝不能再假装"正在重连"——直接报错并停下,否则用户只会看到永远转圈。
2427
+ setStatus({ phase: 'timeout', text: `${T.timeout}:${r.error?.message || r.error || '未知原因'}` });
2378
2428
  setRestarting(false);
2379
2429
  return;
2380
2430
  }
@@ -32,6 +32,8 @@ export const BRIDGE_ENDPOINTS = {
32
32
  authRegenerateToken: 'authRegenerateToken',
33
33
  authAdminUnlock: 'authAdminUnlock',
34
34
  authAdminLock: 'authAdminLock',
35
+ // 首次启用引导:用户确认已了解后置位,避免重复打扰
36
+ dismissFirstRunGuide: 'dismissFirstRunGuide',
35
37
  // 平台管理器(多 IM 平台统一接口)
36
38
  listPlatforms: 'listPlatforms',
37
39
  platformLogin: 'platformLogin',