dsh-zen-remote 1.1.16 → 2.0.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 (255) hide show
  1. package/AGENTS.md +54 -9
  2. package/README.md +249 -190
  3. package/README.zh-CN.md +247 -201
  4. package/assets/home.png +0 -0
  5. package/assets/info.png +0 -0
  6. package/assets/pairing.png +0 -0
  7. package/assets/session.png +0 -0
  8. package/assets/sheet.png +0 -0
  9. package/cordis.patch.yml +12 -11
  10. package/cordis.patch.yml.example +24 -9
  11. package/docs/interface.md +57 -2
  12. package/docs/remote-access.en.md +99 -58
  13. package/docs/remote-access.md +99 -42
  14. package/dsh-push.mjs +67 -54
  15. package/lan-gate.mjs +88 -20
  16. package/lib/activity.js +168 -0
  17. package/lib/activity.js.map +1 -0
  18. package/lib/admin-routes.js +526 -0
  19. package/lib/admin-routes.js.map +1 -0
  20. package/lib/client-pairing.js +224 -0
  21. package/lib/client-pairing.js.map +1 -0
  22. package/lib/client-routes.js +674 -0
  23. package/lib/client-routes.js.map +1 -0
  24. package/lib/client.js +10051 -4777
  25. package/lib/config.js +346 -0
  26. package/lib/config.js.map +1 -0
  27. package/lib/fetch-route-intercept.js +418 -0
  28. package/lib/fetch-route-intercept.js.map +1 -0
  29. package/lib/fingerprint.js +363 -0
  30. package/lib/fingerprint.js.map +1 -0
  31. package/lib/http.js +69 -0
  32. package/lib/http.js.map +1 -0
  33. package/lib/index.js +675 -54
  34. package/lib/index.js.map +1 -1
  35. package/lib/intercept-shape.js +81 -0
  36. package/lib/intercept-shape.js.map +1 -0
  37. package/lib/intercept.js +3040 -0
  38. package/lib/intercept.js.map +1 -0
  39. package/lib/lan-gate-server.cjs +532 -121
  40. package/lib/merge-streams.js +1165 -0
  41. package/lib/merge-streams.js.map +1 -0
  42. package/lib/relay-access.js +496 -0
  43. package/lib/relay-access.js.map +1 -0
  44. package/lib/relay-client.js +1303 -0
  45. package/lib/relay-client.js.map +1 -0
  46. package/lib/relay-filter.js +282 -0
  47. package/lib/relay-filter.js.map +1 -0
  48. package/lib/relay-server.js +1792 -0
  49. package/lib/relay-server.js.map +1 -0
  50. package/lib/restart-fields.js +34 -0
  51. package/lib/restart-fields.js.map +1 -0
  52. package/lib/restart-watcher.js +106 -0
  53. package/lib/restart-watcher.js.map +1 -0
  54. package/lib/session-reference.js +157 -0
  55. package/lib/session-reference.js.map +1 -0
  56. package/lib/share-export.js +16 -1
  57. package/lib/share-export.js.map +1 -1
  58. package/lib/share-ops.js +86 -0
  59. package/lib/share-ops.js.map +1 -0
  60. package/lib/share-store.js +294 -0
  61. package/lib/share-store.js.map +1 -0
  62. package/lib/types/activity.d.ts +99 -0
  63. package/lib/types/activity.d.ts.map +1 -0
  64. package/lib/types/admin-routes.d.ts +129 -0
  65. package/lib/types/admin-routes.d.ts.map +1 -0
  66. package/lib/types/client/MobileDrawerFooter.d.ts.map +1 -1
  67. package/lib/types/client/MobileHome.d.ts +1 -1
  68. package/lib/types/client/MobileHome.d.ts.map +1 -1
  69. package/lib/types/client/MobileHomeChips.d.ts +1 -1
  70. package/lib/types/client/MobileHomeChips.d.ts.map +1 -1
  71. package/lib/types/client/MobileSessionHeader.d.ts +13 -2
  72. package/lib/types/client/MobileSessionHeader.d.ts.map +1 -1
  73. package/lib/types/client/MobileSessionInfo.d.ts +20 -3
  74. package/lib/types/client/MobileSessionInfo.d.ts.map +1 -1
  75. package/lib/types/client/RemoteComposerBanner.d.ts +42 -0
  76. package/lib/types/client/RemoteComposerBanner.d.ts.map +1 -0
  77. package/lib/types/client/RemoteHeaderIcon.d.ts +37 -0
  78. package/lib/types/client/RemoteHeaderIcon.d.ts.map +1 -0
  79. package/lib/types/client/RemoteShareMenu.d.ts +28 -0
  80. package/lib/types/client/RemoteShareMenu.d.ts.map +1 -0
  81. package/lib/types/client/RemoteStatusIcon.d.ts +36 -0
  82. package/lib/types/client/RemoteStatusIcon.d.ts.map +1 -0
  83. package/lib/types/client/compat/desktop.d.ts +21 -0
  84. package/lib/types/client/compat/desktop.d.ts.map +1 -0
  85. package/lib/types/client/compat/types.d.ts +57 -0
  86. package/lib/types/client/compat/types.d.ts.map +1 -1
  87. package/lib/types/client/current-session-report.d.ts +37 -0
  88. package/lib/types/client/current-session-report.d.ts.map +1 -0
  89. package/lib/types/client/effects/header-status.d.ts.map +1 -1
  90. package/lib/types/client/effects/keyboard-guard.d.ts.map +1 -1
  91. package/lib/types/client/effects/permission-sheet.d.ts +39 -0
  92. package/lib/types/client/effects/permission-sheet.d.ts.map +1 -0
  93. package/lib/types/client/effects/remote-session.d.ts +25 -0
  94. package/lib/types/client/effects/remote-session.d.ts.map +1 -0
  95. package/lib/types/client/effects/turn-fold.d.ts +9 -0
  96. package/lib/types/client/effects/turn-fold.d.ts.map +1 -1
  97. package/lib/types/client/index.d.ts.map +1 -1
  98. package/lib/types/client/locales.d.ts +187 -2
  99. package/lib/types/client/locales.d.ts.map +1 -1
  100. package/lib/types/client/remote-fetch.d.ts +51 -0
  101. package/lib/types/client/remote-fetch.d.ts.map +1 -0
  102. package/lib/types/client/remote-group-expansion.d.ts +41 -0
  103. package/lib/types/client/remote-group-expansion.d.ts.map +1 -0
  104. package/lib/types/client/remote-share-css.d.ts +19 -0
  105. package/lib/types/client/remote-share-css.d.ts.map +1 -0
  106. package/lib/types/client/remote-share-register.d.ts +113 -0
  107. package/lib/types/client/remote-share-register.d.ts.map +1 -0
  108. package/lib/types/client/remote-status-css.d.ts +15 -0
  109. package/lib/types/client/remote-status-css.d.ts.map +1 -0
  110. package/lib/types/client/remote-status-register.d.ts +46 -0
  111. package/lib/types/client/remote-status-register.d.ts.map +1 -0
  112. package/lib/types/client/settings/SettingsSection.d.ts +46 -0
  113. package/lib/types/client/settings/SettingsSection.d.ts.map +1 -0
  114. package/lib/types/client/settings/register-settings.d.ts +58 -0
  115. package/lib/types/client/settings/register-settings.d.ts.map +1 -0
  116. package/lib/types/client/settings/settings-css.d.ts +10 -0
  117. package/lib/types/client/settings/settings-css.d.ts.map +1 -0
  118. package/lib/types/client/share/share-preview.d.ts.map +1 -1
  119. package/lib/types/client/styles/composer.css.d.ts +1 -1
  120. package/lib/types/client/styles/composer.css.d.ts.map +1 -1
  121. package/lib/types/client/styles/header.css.d.ts +1 -1
  122. package/lib/types/client/styles/header.css.d.ts.map +1 -1
  123. package/lib/types/client/styles/info.css.d.ts +1 -1
  124. package/lib/types/client/styles/info.css.d.ts.map +1 -1
  125. package/lib/types/client/styles/remote-session.css.d.ts +2 -0
  126. package/lib/types/client/styles/remote-session.css.d.ts.map +1 -0
  127. package/lib/types/client-data/current-session.d.ts +41 -0
  128. package/lib/types/client-data/current-session.d.ts.map +1 -0
  129. package/lib/types/client-data/remote-group-expansion.d.ts +76 -0
  130. package/lib/types/client-data/remote-group-expansion.d.ts.map +1 -0
  131. package/lib/types/client-data/remote-status.d.ts +148 -0
  132. package/lib/types/client-data/remote-status.d.ts.map +1 -0
  133. package/lib/types/client-data/settings-form.d.ts +774 -0
  134. package/lib/types/client-data/settings-form.d.ts.map +1 -0
  135. package/lib/types/client-data/shares.d.ts +211 -0
  136. package/lib/types/client-data/shares.d.ts.map +1 -0
  137. package/lib/types/client-pairing.d.ts +114 -0
  138. package/lib/types/client-pairing.d.ts.map +1 -0
  139. package/lib/types/client-routes.d.ts +112 -0
  140. package/lib/types/client-routes.d.ts.map +1 -0
  141. package/lib/types/config.d.ts +200 -0
  142. package/lib/types/config.d.ts.map +1 -0
  143. package/lib/types/fetch-route-intercept.d.ts +156 -0
  144. package/lib/types/fetch-route-intercept.d.ts.map +1 -0
  145. package/lib/types/fingerprint.d.ts +164 -0
  146. package/lib/types/fingerprint.d.ts.map +1 -0
  147. package/lib/types/http.d.ts +41 -0
  148. package/lib/types/http.d.ts.map +1 -0
  149. package/lib/types/index.d.ts +56 -24
  150. package/lib/types/index.d.ts.map +1 -1
  151. package/lib/types/intercept-shape.d.ts +32 -0
  152. package/lib/types/intercept-shape.d.ts.map +1 -0
  153. package/lib/types/intercept.d.ts +432 -0
  154. package/lib/types/intercept.d.ts.map +1 -0
  155. package/lib/types/merge-streams.d.ts +393 -0
  156. package/lib/types/merge-streams.d.ts.map +1 -0
  157. package/lib/types/relay-access.d.ts +310 -0
  158. package/lib/types/relay-access.d.ts.map +1 -0
  159. package/lib/types/relay-client.d.ts +274 -0
  160. package/lib/types/relay-client.d.ts.map +1 -0
  161. package/lib/types/relay-filter.d.ts +103 -0
  162. package/lib/types/relay-filter.d.ts.map +1 -0
  163. package/lib/types/relay-server.d.ts +208 -0
  164. package/lib/types/relay-server.d.ts.map +1 -0
  165. package/lib/types/restart-fields.d.ts +22 -0
  166. package/lib/types/restart-fields.d.ts.map +1 -0
  167. package/lib/types/restart-watcher.d.ts +83 -0
  168. package/lib/types/restart-watcher.d.ts.map +1 -0
  169. package/lib/types/session-reference.d.ts +66 -0
  170. package/lib/types/session-reference.d.ts.map +1 -0
  171. package/lib/types/share-export.d.ts +14 -1
  172. package/lib/types/share-export.d.ts.map +1 -1
  173. package/lib/types/share-ops.d.ts +79 -0
  174. package/lib/types/share-ops.d.ts.map +1 -0
  175. package/lib/types/share-store.d.ts +87 -0
  176. package/lib/types/share-store.d.ts.map +1 -0
  177. package/lib/types/virtual-id.d.ts +38 -0
  178. package/lib/types/virtual-id.d.ts.map +1 -0
  179. package/lib/virtual-id.js +52 -0
  180. package/lib/virtual-id.js.map +1 -0
  181. package/llms.txt +17 -14
  182. package/package.json +60 -50
  183. package/scripts/build-client.mjs +5 -3
  184. package/scripts/check-host-shadow.mjs +19 -2
  185. package/scripts/check-settings-form.mjs +933 -0
  186. package/scripts/check-shares.mjs +397 -0
  187. package/scripts/check-upload-endpoint.mjs +59 -3
  188. package/scripts/sync-doc-version.mjs +8 -15
  189. package/scripts/verify-fingerprint-live.mjs +111 -0
  190. package/src/activity.ts +218 -0
  191. package/src/admin-routes.ts +627 -0
  192. package/src/client/MobileAttachButton.tsx +2 -2
  193. package/src/client/MobileAttachChips.tsx +3 -3
  194. package/src/client/MobileDrawerFooter.tsx +7 -4
  195. package/src/client/MobileHome.tsx +20 -16
  196. package/src/client/MobileHomeChips.tsx +16 -16
  197. package/src/client/MobileNavOverlay.tsx +2 -2
  198. package/src/client/MobileNavToggle.tsx +3 -3
  199. package/src/client/MobileSessionHeader.tsx +63 -42
  200. package/src/client/MobileSessionInfo.tsx +118 -17
  201. package/src/client/RemoteComposerBanner.tsx +76 -0
  202. package/src/client/RemoteHeaderIcon.tsx +103 -0
  203. package/src/client/RemoteShareMenu.tsx +57 -0
  204. package/src/client/RemoteStatusIcon.tsx +91 -0
  205. package/src/client/compat/desktop.ts +22 -0
  206. package/src/client/compat/types.ts +62 -0
  207. package/src/client/current-session-report.ts +164 -0
  208. package/src/client/effects/header-status.ts +21 -17
  209. package/src/client/effects/keyboard-guard.ts +65 -1
  210. package/src/client/effects/permission-sheet.ts +149 -0
  211. package/src/client/effects/remote-session.ts +68 -0
  212. package/src/client/effects/turn-fold.ts +52 -0
  213. package/src/client/index.tsx +151 -36
  214. package/src/client/locales.ts +396 -2
  215. package/src/client/remote-fetch.ts +125 -0
  216. package/src/client/remote-group-expansion.ts +190 -0
  217. package/src/client/remote-share-css.ts +54 -0
  218. package/src/client/remote-share-register.ts +244 -0
  219. package/src/client/remote-status-css.ts +64 -0
  220. package/src/client/remote-status-register.ts +107 -0
  221. package/src/client/settings/SettingsSection.tsx +1229 -0
  222. package/src/client/settings/register-settings.ts +116 -0
  223. package/src/client/settings/settings-css.ts +236 -0
  224. package/src/client/share/share-preview.tsx +6 -3
  225. package/src/client/share/share-sheet.tsx +3 -3
  226. package/src/client/styles/composer.css.ts +119 -26
  227. package/src/client/styles/header.css.ts +7 -7
  228. package/src/client/styles/info.css.ts +74 -0
  229. package/src/client/styles/remote-session.css.ts +251 -0
  230. package/src/client-data/current-session.ts +57 -0
  231. package/src/client-data/remote-group-expansion.ts +145 -0
  232. package/src/client-data/remote-status.ts +374 -0
  233. package/src/client-data/settings-form.ts +1371 -0
  234. package/src/client-data/shares.ts +467 -0
  235. package/src/client-pairing.ts +244 -0
  236. package/src/client-routes.ts +732 -0
  237. package/src/config.ts +447 -0
  238. package/src/fetch-route-intercept.ts +510 -0
  239. package/src/fingerprint.ts +408 -0
  240. package/src/http.ts +68 -0
  241. package/src/index.ts +719 -62
  242. package/src/intercept-shape.ts +87 -0
  243. package/src/intercept.ts +3362 -0
  244. package/src/merge-streams.ts +1272 -0
  245. package/src/relay-access.ts +669 -0
  246. package/src/relay-client.ts +1514 -0
  247. package/src/relay-filter.ts +291 -0
  248. package/src/relay-server.ts +2025 -0
  249. package/src/restart-fields.ts +36 -0
  250. package/src/restart-watcher.ts +140 -0
  251. package/src/session-reference.ts +156 -0
  252. package/src/share-export.ts +26 -1
  253. package/src/share-ops.ts +114 -0
  254. package/src/share-store.ts +373 -0
  255. package/src/virtual-id.ts +59 -0
package/README.zh-CN.md CHANGED
@@ -1,10 +1,17 @@
1
1
  <h1 align="center">dsh-zen-remote</h1>
2
- <p align="center">把 DeepSeek Harness 变成一个能从公网安全访问的手机 App:移动端界面重排 + 配对认证网关 + 装到主屏 + 锁屏推送。</p>
2
+ <p align="center">一个 DeepSeek Harness 插件、两种角色。<b>主服务端</b>把 DSH 变成手机能安全访问的 PWA(手机界面、配对网关、锁屏推送),并把选定的会话共享给已配对的桌面端;<b>子客户端</b>跑在另一台 DSH 桌面端里,把服务端共享的会话直接摆进自己的侧边栏——完整历史、实时进度、发消息、审批、终端,全部在服务端执行。</p>
3
3
 
4
4
  <p align="center">
5
+ <a href="https://www.npmjs.com/package/dsh-zen-remote"><img src="https://img.shields.io/npm/v/dsh-zen-remote?style=flat-square&color=5B4CF0&logo=npm&label=npm" alt="npm version"></a>
6
+ <a href="https://www.npmjs.com/package/dsh-zen-remote"><img src="https://img.shields.io/npm/dm/dsh-zen-remote?style=flat-square&color=0B7285&label=downloads" alt="npm downloads"></a>
7
+ <a href="https://github.com/KyoMio/dsh-zen-remote/actions/workflows/publish.yml"><img src="https://img.shields.io/github/actions/workflow/status/KyoMio/dsh-zen-remote/publish.yml?style=flat-square&label=publish&logo=githubactions&logoColor=white" alt="publish workflow"></a>
5
8
  <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-0B7285?style=flat-square" alt="MIT"></a>
6
- <img src="https://img.shields.io/badge/release-v1.1.16-5B4CF0?style=flat-square" alt="v1.1.16">
7
- <img src="https://img.shields.io/badge/DSH-Web%20Profile-5B4CF0?style=flat-square" alt="DSH Web Profile">
9
+ <br>
10
+ <img src="https://img.shields.io/badge/DeepSeek%20Harness-0.1.7%20%E2%80%93%200.2.x-4D6BFE?style=flat-square" alt="DeepSeek Harness 0.1.7 – 0.2.x">
11
+ <img src="https://img.shields.io/node/v/dsh-zen-remote?style=flat-square&color=339933&logo=nodedotjs&logoColor=white" alt="Node">
12
+ <a href="https://www.npmjs.com/package/dsh-zen-remote#provenance"><img src="https://img.shields.io/badge/npm-provenance%20signed-CB3837?style=flat-square&logo=npm&logoColor=white" alt="npm provenance"></a>
13
+ <img src="https://img.shields.io/badge/PWA-Web%20Push-5A0FC8?style=flat-square&logo=pwa&logoColor=white" alt="PWA + Web Push">
14
+ <img src="https://img.shields.io/badge/TypeScript-strict-3178C6?style=flat-square&logo=typescript&logoColor=white" alt="TypeScript strict">
8
15
  </p>
9
16
 
10
17
  <p align="center"><a href="README.md">English</a></p>
@@ -17,215 +24,233 @@
17
24
  | --- | --- |
18
25
  | ![composer 权限 sheet](assets/sheet.png) | ![配对页](assets/pairing.png) |
19
26
 
20
- > 截图为 390×844 手机视口、浅色主题;深浅主题均适配。配对页是网关自绘页面,固定深色设计。
27
+ > 截图为 390×844 手机视口、浅色主题;深浅主题均适配。配对页由网关绘制,按 DSH 官方风格、跟随系统浅色 / 深色。
21
28
 
22
29
  ---
23
30
 
24
- ## 安装
31
+ ## 一个插件、两种角色
25
32
 
26
- ```sh
27
- dsh plugin add dsh-zen-remote
28
- ```
33
+ 同一个包、同一行插件。跑哪一半由 DSH「插件」页设置区块里的 `role` 决定:
29
34
 
30
- 装完重启 `dsh web`,手机界面与网关一起生效,不需要再手写任何配置行。
35
+ | | **主服务端**(`role: host`,默认) | **子客户端**(`role: client`) |
36
+ | --- | --- | --- |
37
+ | 机器 | 跑会话的那台 | 另一台装了 DSH 桌面端的电脑 |
38
+ | 加载什么 | 手机界面、网关(子进程)、Web 推送、会话共享、中继服务端 | 中继客户端 + 侧边栏与会话页的远程部件 |
39
+ | 不加载什么 | — | 不起网关、不注册推送、手机界面不生效——不和主服务端抢端口 |
31
40
 
32
- > **兼容性——同一份产物,两个运行时。** 在 DSH `0.1.1-rc.2` 与 `0.1.2-rc.1`(web profile)上均实机验证,2026-09-04。
33
- >
34
- > 0.1.2 删掉了 `@deepseek-ai/dsh-client-runtime`,并把网页界面挪到签名 cookie 之后——照原样会让手机界面加载失败、手机经网关也进不来。本插件改为自带 store 实现而不 import 那个包;对 0.1.2 搬了家的服务(`uiWorkspace`、`uiSession`)按存在探测、取不到就走 0.1.1 的老路;回合事件 `session.events` 和 `session.snapshotEvents()` 两种读法都认;浏览器 token 交换由网关代手机完成——这些在 0.1.1 上全部惰性,因为那边根本没有 token 端点。
35
- >
36
- > 0.1.2 上有一点要知道:网页界面现在会拒绝 `Host` 不受信的请求。经本插件网关访问不受影响(网关把每个请求都以 `127.0.0.1` 的身份递上去),但拿浏览器直接开 `http://<局域网IP>:3080` 会得到 403,除非启动时加 `dsh web --trusted-host <host>`。
41
+ 已配对的**桌面应用端**只看得到服务端开启了远程的会话,在本地工作区后面按「服务端名 · 工作区名」分组。服务端的工作区只有其中**至少一个会话开启了远程**才会出现——没动过的工作区不会显示成空分组;已经显示过的分组里会话全部关闭远程后,空分组会留到刷新页面为止。子客户端本地的会话完全不受影响。
37
42
 
38
- 卸载:`dsh plugin remove dsh-zen-remote`(或从 profile 的 `dependencies` 与 `bundles` 里删掉那两行),重启 `dsh web` 即恢复原状;要清掉配对数据再删 `~/.dsh/lan-gate-state.json` 与 `~/.dsh/lan-gate.config.json`。
43
+ ---
39
44
 
40
- <details>
41
- <summary>手动写法 / 本地开发</summary>
45
+ ## 远程连接
46
+
47
+ **适合什么场景**:一台电脑常开、跑会话(主服务端),另一台电脑的 DSH 桌面端(子客户端)远程查看、接着干活——历史、实时进度、发消息、审批、终端都能用,所有执行都发生在主服务端。手机、平板走的是另一条路:浏览器经网关访问主服务端(Web 应用端),见[配置公网访问](#配置公网访问主服务端)。
48
+
49
+ **怎么用**,四步:
50
+
51
+ 1. **配对**:主服务端设置页生成「桌面应用端」配对码,子客户端设置页填服务端地址和配对码。逐步说明见下面的[设置页配置引导](#设置页配置引导)。
52
+ 2. **开启远程**:远程按会话逐个开。主服务端在会话的「…」菜单里点「开启远程」(也可以用标题行的地球图标、设置页的共享列表,见[会话共享](#会话共享))。
53
+
54
+ ![主服务端在会话的「…」菜单里开启远程](docs/images/host-share-menu.png)
55
+
56
+ 3. **在子客户端使用**:侧边栏本地工作区后面出现「服务端设备名称 · 工作区名」分组,点开就像本地会话一样用;标题行的地球图标显示连接状态(在线 / 离线 / 版本有差异)。服务端上没有开启远程会话的工作区不会出现。
57
+
58
+ ![子客户端侧边栏里的「书房 · Desktop」分组,标题行是远程连接状态图标](docs/images/client-sidebar.png)
59
+
60
+ 4. **关闭与恢复**:服务端关闭远程、会话闲置休眠(默认 48 小时无动静)、或在子客户端自己关闭时,正打开的会话页会显示「远程已关闭」横幅并禁用输入,切到别的会话后它从列表里隐藏;重新开启远程后自动回来。断线期间页面保持原样,连上后自动续上。
61
+
62
+ ![远程被关闭后,会话页输入框上方的「远程已关闭」横幅](docs/images/client-remote-closed.png)
63
+
64
+ **手机上也能开关**:用手机(Web 应用端)访问主服务端时,会话页的信息卡片里有一行「远程访问」,显示是否已开启、剩余闲置时间,点开关即可开启或关闭。
65
+
66
+ <p align="center"><img src="docs/images/mobile-info-remote.png" alt="手机会话信息卡片里的「远程访问」开关" width="320"></p>
67
+
68
+ 能做什么、不能做什么,见[功能](#功能)和[已知限制](#已知限制);已配对的桌面应用端被视为可信设备,信任边界见[安全模型](#安全模型)。
69
+
70
+ > 以上截图中,红框圈出的是本插件加入 DSH 界面的部分;会话标题为演示用,对话内容已模糊处理。
71
+
72
+ ---
73
+
74
+ ## 安装
75
+
76
+ **界面安装**(桌面端 App 与 Web 端都适用;桌面端是 2.0 主服务端的实际形态):左侧「插件」页 →「添加插件」→ 输入 `dsh-zen-remote` → 装完点「立即启用」,然后重启桌面端 App(Web 端重启 `dsh web`)。
77
+
78
+ **Web 服务命令行**(无头机器上跑 `dsh web` 的主服务端,手机访问也指向这里):
79
+
80
+ ```sh
81
+ dsh plugin --profile web add dsh-zen-remote
82
+ # 然后重启 dsh web
83
+ ```
84
+
85
+ `dsh plugin` 必须用 `--profile` 指明装进哪个 profile,不带会直接报错。桌面端的 `desktop` profile 由 App 独占管理,命令行会拒绝 `--profile desktop`,所以桌面端只能用上面的界面安装。
42
86
 
43
- 手动改 `~/.dsh/profiles/web/package.json`——`dependencies` 一行、`bundles` 一行:
87
+ **手工改 profile**(本地开发、`link:` 装法):把依赖写进 profile 的 `package.json`(桌面端在 `~/.dsh/profiles/desktop`,Web 端在 `~/.dsh/profiles/web`),装完重启:
44
88
 
45
89
  ```jsonc
90
+ // ~/.dsh/profiles/desktop/package.json
46
91
  {
47
92
  "dependencies": {
48
- "dsh-zen-remote": "^1.1.16" // 本地开发换成 "link:/path/to/dsh-zen-remote"
49
- },
50
- "dsh": { "profile": { "bundles": [
51
- "@deepseek-ai/dsh-base",
52
- "@deepseek-ai/dsh-web-app",
53
- "dsh-zen-remote"
54
- ] } }
93
+ "dsh-zen-remote": "^2.0.0" // 本地开发换成 "link:/path/to/dsh-zen-remote"
94
+ }
55
95
  }
56
96
  ```
57
97
 
58
98
  ```sh
59
- cd ~/.dsh/profiles/web && pnpm install
60
- # 重启 dsh web
99
+ cd ~/.dsh/profiles/desktop && pnpm install
100
+ # 然后重启 DSH 桌面端 App(Web 端重启 dsh web)
61
101
  ```
62
102
 
63
- 不想走 profile 安装流程的静态挂载写法见 [`cordis.patch.yml.example`](cordis.patch.yml.example)。
103
+ `pnpm install` 只装包、不启用。还要到「插件」页打开它的开关,或者自己把 `"dsh-zen-remote"` 追加到同一个文件 `dsh.profile.bundles` 数组的末尾(数组里原有的项别动)。界面安装和 `dsh plugin` 会自动写这一项,手工装法要自己补。
104
+
105
+ 无论哪种装法,组合层都只有**一行**。2.0.0 起挂载层只剩 `dsh-zen-remote` 一行,主入口按角色自己加载网关与推送子插件。不要再把旧的 `dsh-zen-remote-gateway` / `dsh-zen-remote-push` 两行加回来:loader 会警告后跳过;真挂两行网关就是两个网关进程抢 3088 端口。
106
+
107
+ <details>
108
+ <summary>静态挂载 / 本地开发</summary>
109
+
110
+ 把 [`cordis.patch.yml.example`](cordis.patch.yml.example) 抄进 profile 的 `cordis.patch.yml`、换成绝对路径即可——同样只有一行,附带的 `config:` 示例列了可写的行配置。用界面或 `dsh plugin` 正常安装的不需要它。
64
111
 
65
112
  </details>
66
113
 
114
+ 卸载:在「插件」页打开 dsh-zen-remote,点「卸载」并确认;Web 服务也可以用命令行 `dsh plugin --profile web remove dsh-zen-remote`;手工装的从 profile 的 `dependencies` 和 `dsh.profile.bundles` 里删掉它。之后重启;要清掉配对与共享状态再删 `~/.dsh/lan-gate-state.json`、`~/.dsh/lan-gate.config.json`、`~/.dsh/zen-remote-shares.json` 与 `~/.dsh/zen-remote-server.json`(服务端 id;删了之后已配对的子客户端会把它当成一台新服务端)。
115
+
67
116
  ---
68
117
 
69
- ## 配置公网访问
118
+ ## 从 1.x 升级
70
119
 
71
- 装完在本机 `127.0.0.1:3080` 就能用手机界面。要从外面访问,按下面三步走。
120
+ 一次性的事实,按踩到的概率排序:
72
121
 
73
- ### 1. 配一个反代中继 HTTPS
122
+ - **三行合并为一行。** profile 补丁里残留的 `dsh-zen-remote-gateway` / `dsh-zen-remote-push` 旧行会被 loader 警告后跳过。原来写在**那两行上**的 `config:` 不再生效——搬到 `dsh-zen-remote` 行,或者直接在插件设置页里改。
123
+ - **1.0 时代在 profile 补丁里手工 insert 过 `dsh-zen-remote/dsh-push.mjs` 那一行的,需要删掉**——主入口现在按角色自动加载推送子插件,残留的那一行会让推送加载两次。
124
+ - **`~/.dsh/lan-gate.config.json` 继续生效。** 无需迁移,它的值会显示在设置页里。每个字段的优先级:环境变量 > 插件行设置 > `lan-gate.config.json` > 内置默认值。插件从不改写、也从不删除这个文件。
125
+ - **已配对设备自动归为 Web 应用端**,继续照常用,不需要重新配对。
126
+ - **管理页搬家了。** `/lan-gate/admin` 现在只剩一段指向插件设置页的说明(对非本机直连的请求仍然 403)。配对码、设备管理、测试推送、会话共享全部在 DSH「插件」页的 dsh-zen-remote 设置区块里。
127
+ - 什么配置都不动的话,插件按主服务端起来,网关、推送、手机界面的行为与 1.1.x 完全一致。
74
128
 
75
- 网关默认只监听 `127.0.0.1:3088`,必须由你自己的反代对外。**家宽没有公网 IP、或者不想开路由器端口**,就跳过 nginx/Caddy 直接看第三个块(Cloudflare Tunnel)。
129
+ ---
76
130
 
77
- <details open>
78
- <summary><b>nginx</b></summary>
131
+ ## 配置公网访问(主服务端)
79
132
 
80
- ```nginx
81
- # http {} 块里加一次
82
- map $http_upgrade $connection_upgrade { default upgrade; '' close; }
133
+ 装完在本机 `127.0.0.1:3080` 就能用手机界面。要从外面访问:给自己的网关配一个终结 TLS 的反代,用 403 自检确认,再配对设备。nginx / Caddy / Cloudflare Tunnel / Lucky 的完整配置见 [docs/remote-access.md](docs/remote-access.md),简版:
83
134
 
84
- server {
85
- listen 443 ssl http2;
86
- server_name dsh.example.com;
135
+ 1. 网关只监听 `127.0.0.1:3088`,由反代(nginx、Caddy、Cloudflare Tunnel、Lucky)终结 HTTPS 并转发。**自检**:用**手机流量**(别连家里 Wi-Fi)访问 `https://你的域名/lan-gate/admin`,正确结果是 **403**。能看到说明页就代表反代没带 `X-Forwarded-*` 头、公网请求被当成了本机用户——先修转发头再往下走。
136
+ 2. 在**服务端这台机器**打开 DSH「插件」页,展开 dsh-zen-remote 设置区块,先选要配的设备角色,再点生成 8 位配对码(10 分钟有效、只能用一次)。
137
+ 3. 手机打开你的 HTTPS 域名,在配对页输入配对码。配对成功即进入 DSH,身份存在长期 Cookie 里。浏览器菜单「添加到主屏幕」装成 App,同意通知权限,agent 干完活推到锁屏。
87
138
 
88
- ssl_certificate /etc/letsencrypt/live/dsh.example.com/fullchain.pem;
89
- ssl_certificate_key /etc/letsencrypt/live/dsh.example.com/privkey.pem;
139
+ ---
90
140
 
91
- location / {
92
- proxy_pass http://127.0.0.1:3088;
93
- proxy_http_version 1.1;
141
+ ## 配对设备
94
142
 
95
- proxy_set_header Upgrade $http_upgrade;
96
- proxy_set_header Connection $connection_upgrade;
143
+ 配对码生成时**绑定设备角色**,兑换时校验角色——拿错通道用码会被明确拒绝,码不被消耗,也不计入错码锁定。
97
144
 
98
- proxy_set_header Host $host;
99
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
100
- proxy_set_header X-Forwarded-Proto $scheme;
145
+ | | **Web 应用端** | **桌面应用端** |
146
+ | --- | --- | --- |
147
+ | 形态 | 手机、平板及各类浏览器,经网关访问 | 装了 DSH 桌面端、zen-remote 设为子客户端角色的计算机 |
148
+ | 可见范围 | 服务端**全部**会话 | **仅**服务端已开启远程的会话(服务端强制) |
149
+ | 配对方式 | 浏览器打开网关地址,在配对页输入配对码 | 在子客户端的插件设置页填服务端地址和配对码 |
101
150
 
102
- proxy_buffering off;
103
- proxy_read_timeout 3600s;
104
- }
105
- }
106
- ```
151
+ **桌面应用端配对**,逐步:
107
152
 
108
- </details>
153
+ 1. 在服务端的插件设置区块选「桌面应用端」,生成配对码。
154
+ 2. 在另一台电脑的 DSH 桌面端里打开插件设置区块,角色切到**子客户端**,填入服务端地址和配对码。
155
+ 3. 地址必须 `https://`,内网地址才允许 http——局域网段(`192.168.x`、`10.x`、`172.16–31.x`)、Tailscale/CGNAT(`100.64.x`)、回环、`localhost` / `*.local`、IPv6 `::1` / `fc00::/7` / `fe80::/10`。公网地址走明文 http 会被拒绝,设备令牌不会明文过公网。
156
+ 4. 配对成功后令牌存在插件行里(密钥字段),客户端自动连接。设置区块显示连接状态——已连接 / 离线 / 版本有差异 / 令牌已吊销——并可以解配。
109
157
 
110
- <details open>
111
- <summary><b>Caddy</b></summary>
158
+ 服务端的设置区块可以改设备名、改设备角色(`set-role`)、单独或全部吊销。吊销立即生效:连接与推送订阅一并失效。桌面应用端够不到任何管理路由——网关只把它的请求转进中继前缀。
112
159
 
113
- ```
114
- dsh.example.com {
115
- reverse_proxy 127.0.0.1:3088
116
- }
117
- ```
160
+ **设备名称**:设置页「角色」卡片里的「设备名称」字段(配置项 `serverName`)两种角色共用:主服务端上它是其他设备侧边栏分组标题里的名字(默认取电脑名);子客户端上它是配对时注册到服务端的名字。名字双向自动同步:服务端改自己的名字,子客户端分组标题约 15 秒内跟随;子客户端保存新名字,服务端设备列表跟着更新;服务端在设备列表里给子客户端改名,子客户端约 30 秒内跟随。子客户端只在主动保存设置时推送自己的名字,其余时候以服务端记录为准;离线时改的名字如果进程在推送前退出,下次启动以服务端记录为准。名字 1–40 个字符。
118
161
 
119
- </details>
162
+ ---
120
163
 
121
- <details open>
122
- <summary><b>没有公网 IP?用 Cloudflare Tunnel</b></summary>
164
+ ## 会话共享
123
165
 
124
- 家宽拿不到公网 IP、或者不想在路由器上开端口时用这个:`cloudflared` 从你这台机器主动连出去,Cloudflare 那边负责域名、证书和入口,路由器一个端口都不用开。免费版够用。
166
+ 远程按会话逐个开启,有四个入口:
125
167
 
126
- 前置:域名托管在 Cloudflare(NS 指过去)。
168
+ 1. 侧边栏会话行的「…」菜单里的「开启远程 / 关闭远程」;
169
+ 2. 会话页标题行的远程图标(点击开关、带二次确认;悬停显示剩余闲置时间;图标带小圆点表示此刻有桌面应用端在看);
170
+ 3. 插件设置区块的「已开启远程的会话」列表,含一键全部关闭;
171
+ 4. 手机(Web 应用端)会话页信息卡片里的「远程访问」开关。
127
172
 
128
- 1. 打开 [Zero Trust 控制台](https://one.dash.cloudflare.com/) → **Networks → Tunnels → Create a tunnel** → 选 **Cloudflared**,起个名字,创建后页面会给你一条带 token 的安装命令;
129
- 2. 在跑 DSH 的这台机器上执行那条命令(就是下面这个形状,token 用页面给的):
173
+ **闲置休眠**:开启远程的会话在 **48 小时**(可配,`idleHours`)内没有任何会话动静——回合开始或结束、发消息、审批或提问得到回答,不分服务端还是子客户端——就自动关闭远程。运行中的回合、等待审批/回答的会话永远不会被扫掉;只开窗口「看着」不算活跃。剩余时间悬停标题行图标可见,设置区块里逐条列出。
130
174
 
131
- ```sh
132
- # macOS / Linux:装成常驻服务,开机自启
133
- cloudflared service install eyJhIjoi...你的token
134
- ```
175
+ **新建会话**:`autoShareNewSessions` 打开时,服务端新建的会话自动开启远程;**经中继**新建的会话(子客户端远程分组里点的)一律自动开启,这条入口的安全性正建立在此之上。子智能体会话与分叉会话自动跟随父会话,不用逐个开启。
135
176
 
136
- 3. 回到隧道详情页 → **Public Hostname** → **Add a public hostname**:
177
+ **「关闭远程」意味着什么**:未开启远程的会话,其列表、历史、实时进度、审批/提问事件,全部在中继处拒绝或过滤——不只是界面上藏起来。@ 引用候选只列已共享会话;发消息、改写排队消息、斜杠命令里内嵌的会话引用指向未共享会话时,整个调用被拒绝。
137
178
 
138
- | 字段 | 填什么 |
139
- | --- | --- |
140
- | Subdomain / Domain | `dsh` / `example.com`(即 `dsh.example.com`) |
141
- | Service Type | `HTTP` |
142
- | URL | `127.0.0.1:3088` |
179
+ **关闭远程后在子客户端的表现**:服务端关闭某个会话的远程(或它闲置休眠)时,该会话直接从子客户端的侧边栏列表里隐藏——按「已归档」处理,在 DSH 里打开「显示已归档」筛选还能看到它。唯一的例外是此刻正打开着的会话页:页面停在原处,composer 上方出现「远程已关闭」横幅并注明原因(服务端关闭 / 闲置休眠 / 已在本机关闭),输入禁用;切到别的会话后它也随之隐藏。服务端重新开启远程(或重新共享)后,会话回到原工作区,打开着的页面自动恢复——横幅消失、输入恢复可用。
143
180
 
144
- 保存后 `https://dsh.example.com` 就通了,证书 Cloudflare 自动签。网关的 `LAN_GATE_HOST` 保持默认 `127.0.0.1` 即可——`cloudflared` 就在本机。
181
+ **断线与恢复**:服务端(或链路)断线期间,已打开的远程会话页保持原状——横幅提示离线、输入禁用;连接恢复后自动续上:会话流与任务列表类面板自动重新拉取最新快照,不用手动刷新。例外是文件树变更与终端面板,断线后需要重新打开。
145
182
 
146
- **装完必须做第 2 步的 403 自检**,这一步对隧道尤其要紧:`cloudflared` 和网关走的是本机回环连接,网关区分「公网访客」和「坐在这台电脑前的你」,全靠隧道有没有带上 `X-Forwarded-For`。`cloudflared` 默认是带的,所以配对墙正常生效;但万一你的版本或配置把它去掉了,公网请求就会被当成本机管理员,配对墙形同虚设——**用手机流量访问 `/lan-gate/admin`,看到 403 才算安全**。
183
+ ---
147
184
 
148
- > 提示:不用设 `LAN_GATE_TRUSTED_PROXIES`——网关本来就把回环来的连接当作可信反代,填 `127.0.0.1` 是空操作,也**不能**替代上面那个自检。
149
- >
150
- > Cloudflare 免费版支持 WebSocket(DSH 对话流需要),单个请求体上限 100MB,高于本插件默认的 20MB 上传上限,不影响使用。
185
+ ## 配置
151
186
 
152
- 命令行流程(`cloudflared tunnel login` / `create` / `route dns` + `config.yml` 里写 ingress)见 [docs/remote-access.md](docs/remote-access.md#cloudflare-tunnel没有公网-ip-时的接入方式)。
153
- </details>
187
+ ### 设置页配置引导
154
188
 
155
- Lucky(路由器/NAS)的配法见 [docs/remote-access.md](docs/remote-access.md#lucky)。反代与网关不在同一台机器时,要把反代出口 IP 填进 `LAN_GATE_TRUSTED_PROXIES`。
189
+ **在哪打开**:DSH 左侧点「插件」→ 插件列表里点 dsh-zen-remote → 在「包含的组件」里再点一次 dsh-zen-remote,下面就是设置区块。
156
190
 
157
- ### 2. 自检
191
+ **通用操作**:字段右上角出现「已覆盖」表示这个值保存在插件设置里(覆盖了配置文件或默认值),点「重置」恢复成下一层的值;改完点卡片底部的「保存」。需要重载的字段会有提示,保存后插件自己重载,不用重启 App。
158
192
 
159
- 用**手机流量**(别连家里 Wi-Fi)访问 `https://你的域名/lan-gate/admin`,正确结果是 **403**。
193
+ **主服务端**
160
194
 
161
- 能看到管理页说明反代没带 `X-Forwarded-*` 头,公网请求被当成了本机用户——回去检查转发头再往下走。
195
+ 1. 「运行角色」选「主服务端」,「设备名称」填一个好认的名字(比如「书房」)——子客户端侧边栏的分组标题、手机上都显示它。保存。
162
196
 
163
- ### 3. 配对设备
197
+ ![主服务端设置页:角色卡片里的运行角色与设备名称](docs/images/host-settings-role.png)
164
198
 
165
- ```sh
166
- # 在跑 DSH 的这台机器上,用本机浏览器打开
167
- open http://127.0.0.1:3088/lan-gate/admin
168
- ```
199
+ 2. 「网关与反代」:只在本机和手机经反代访问时保持默认即可,公网访问见[配置公网访问](#配置公网访问主服务端)。要让局域网里的子客户端**直连**网关,把「监听地址」改成 `0.0.0.0`(必须包含本机回环,设置页靠回环地址管理网关)。
200
+ 3. 「配对」:选「Web 应用端」(手机、浏览器)或「桌面应用端」(另一台 DSH 桌面端),点「生成配对码」,10 分钟内在对方那里输入。「设备」列表里可以改名、改设备角色、吊销。
169
201
 
170
- 1. 点「生成配对码」,得到 8 位码(10 分钟有效、只能用一次);
171
- 2. 手机打开你的 HTTPS 域名,在配对页输入这个码;
172
- 3. 配对成功即进入 DSH,身份存在长期 Cookie 里,换网络不掉线;
173
- 4. 浏览器菜单「添加到主屏幕」装成 App;
174
- 5. 同意通知权限,agent 干完活推到锁屏。
202
+ ![生成配对码与设备列表](docs/images/host-pairing.png)
175
203
 
176
- 管理页还能改设备名、设备类型,或单独/全部吊销设备。
204
+ 4. 「远程共享」:闲置休眠时长、是否自动共享新会话。卡片下方的「已开启远程的会话」列出当前开着远程的会话、剩余闲置时间、是否有设备正在查看,可以逐个或全部关闭。
177
205
 
178
- ---
206
+ ![已开启远程的会话列表](docs/images/host-settings-shares.png)
179
207
 
180
- ## 可选配置
208
+ 5. 「推送」:推送内容摘要、回合结束推送等,按需开启,说明见[通知什么时候会响](#通知什么时候会响)。
181
209
 
182
- 环境变量,或 `~/.dsh/lan-gate.config.json`(键名是变量去前缀转小驼峰,如 `port` / `trustedProxies`;显式环境变量优先)。改完重启 `dsh web`。
210
+ **子客户端**
183
211
 
184
- | 变量 | 默认 | 说明 |
185
- | --- | --- | --- |
186
- | `LAN_GATE_PORT` | `3088` | 网关端口;被占用自动往上试(最多 +20) |
187
- | `LAN_GATE_HOST` | `127.0.0.1` | 监听地址;反代不在本机时才需要放开 |
188
- | `LAN_GATE_TARGET_PORT` | `3080` | 本机 DSH Web UI 端口 |
189
- | `LAN_GATE_RATE_LIMIT` | `120` | 未配对请求的每分钟上限(按真实客户端 IP) |
190
- | `LAN_GATE_TRUSTED_PROXIES` | 空 | 逗号分隔 IP;反代不在本机时必填 |
191
- | `LAN_GATE_VAPID_SUBJECT` | `mailto:admin@localhost` | 推送联系人。**iOS 必须改成真实邮箱或 https 网址**,否则 Apple 拒发 |
192
- | `LAN_GATE_LANG` | `auto` | 配对页、管理页、推送开关卡片的语言。`auto` 跟随浏览器的 `Accept-Language`(认不出就用中文),`zh`/`en` 写死 |
193
- | `DSH_PUSH_TURN_END` | **关** | 设 `1` 让「回合结束」也推一条。默认不推——回合结束不代表需要你(1.0.3 之前是默认推的,这是行为变更)。等授权、等回答这两类通知不受它影响,永远推 |
194
- | `DSH_PUSH_EVENTS` | `agent/turn-stopping` | 「回合结束」算哪些事件,逗号分隔;只在 `DSH_PUSH_TURN_END=1` 时有意义 |
195
- | `DSH_PUSH_DEBOUNCE_MS` | `15000` | 两条自动推送的最小间隔;等授权/等回答的通知不受压制 |
196
- | `DSH_PUSH_SUMMARY` | 关 | 设 `1` 让通知带上本回合的最终回复(只取正文,不含思考过程;截 120 字)和提问原文 |
197
- | `DSH_PUSH_TOOL` | 开 | 设 `0` 关掉模型可调用的 `push_notify` 工具 |
198
- | `DSH_PUSH_LANG` | `zh` | 推送通知文案的语言。通知没有「读者是谁」的信号,宿主进程也拿不到可靠的系统语言(launchd 不带 `LANG`),所以这里不自动探测:设 `en` 才是英文 |
199
- | `DSH_PUSH_APPROVAL_GRACE_MS` | `5000` | 「等授权」推送前的等待窗口。装了会自动答复审批的插件时(如 dsh-auto-approve),要等它答完再决定推不推——答完了就不推。判定器比这个慢就还是会推,那时把它调大 |
200
-
201
- 上传大小上限(默认 20MB)在插件行的 `config.maxUploadBytes` 里改。
202
-
203
- 想在电脑端也启用回合过程折叠(默认只在手机宽度生效),在插件行的 `config.turnFoldDesktop` 里设 `true`——即在 profile 的 `cordis.patch.yml` 加一条:
204
-
205
- ```yaml
206
- - id: dsh-zen-remote
207
- config:
208
- turnFoldDesktop: true
209
- ```
212
+ 1. 「运行角色」选「子客户端」,保存——插件自动重载,设置页换成子客户端的内容。
213
+ 2. 「设备名称」填这台电脑的名字(比如「客厅」),配对时它会登记到服务端的设备列表里。保存。
214
+ 3. 「连接服务端」:填服务端的网关地址(局域网例如 `http://192.168.1.10:3088`,公网必须 `https://`)和服务端生成的「桌面应用端」配对码,点「配对」。
215
+ 4. 「连接状态」显示「已连接到『服务端设备名称』」就完成了,侧边栏随即出现服务端的分组。「诊断」区列出请求拦截器状态和最近的远程调用失败,排障先看这里。
210
216
 
211
- 改完重启 `dsh web`。不改服务端配置的话,单个浏览器也可以访问一次 `?mobile-nav-turn-fold=1` 自己开启(`=0` 关闭,按浏览器记忆)。
217
+ ![子客户端设置页:角色、设备名称、连接服务端与连接状态](docs/images/client-settings.png)
212
218
 
213
- **软键盘抬升的三个校准值**。少数手机上,键盘弹出时系统压根不告诉浏览器键盘有多高(实测过:某些第三方输入法 + Chrome;小米浏览器装的 PWA 壳)。这时插件没有任何可测的信号,只能按估算把输入框抬起来。估算值是照一台报告过的机器定的,别的机器可能偏高或偏低,所以三个数都能在插件行里改:
219
+ > 截图中的地址、配对码为演示值。
214
220
 
215
- | 配置项 | 默认 | 含义 | 允许范围 |
216
- | --- | --- | --- | --- |
217
- | `keyboardLiftRatio` | `0.42` | 抬升高度按屏幕高度的这个比例估算 | 0 ~ 1 |
218
- | `keyboardLiftMaxPx` | `400` | 估算值的上限(像素),防止在长屏手机上把输入框顶到屏幕中间 | 0 ~ 2000 |
219
- | `keyboardSafetyPadPx` | `15` | 键盘顶部再留出的一点余量,**仅安卓**。第三方输入法常常少报自己的高度(把键盘上方那条工具栏漏掉),这一点余量就是补它的 | 0 ~ 200 |
221
+ ### 字段参考
220
222
 
221
- ```yaml
222
- - id: dsh-zen-remote
223
- config:
224
- keyboardLiftRatio: 0.45
225
- keyboardSafetyPadPx: 30
226
- ```
223
+ 全部在 DSH「插件」页 → dsh-zen-remote 设置区块里改。每个字段按此顺序取第一个合法值:**环境变量 > 插件行设置 > `~/.dsh/lan-gate.config.json` > 默认值**;某层的值类型/范围/枚举不合法时该层视为未设置。改完需要重启网关/推送的字段(`role`、`port`、`host`、`targetPort`、`rateLimit`、`trustedProxies`、`vapidSubject`、`lang` 与推送各字段),插件会自己重载插件行——不用手动重启 App;其余字段立即生效。
227
224
 
228
- 怎么调:输入框抬得**不够**(还被键盘挡住一截)就调大 `keyboardLiftRatio`,一次加 0.03 试;抬得**过头**(输入框和键盘之间空出一条)就调小。只差一点点(几十像素以内、且是安卓)优先加 `keyboardSafetyPadPx`。三个值一个都不写就是现在的行为,不受影响;写超出范围的值会被自动收进上表的区间,不会把输入框顶出屏幕。正常手机走的是实测路径,这几个值对它们完全没有影响。
225
+ 插件行字段(与 `src/config.ts` 一致):
226
+
227
+ | 字段 | 默认 | 说明 |
228
+ | --- | --- | --- |
229
+ | `role` | `host` | `host` 跑网关 + 推送 + 共享;`client` 连接服务端。不是精确的 `client` 就按 `host` 处理 |
230
+ | `port` | `3088` | 网关端口;被占用自动往上试(最多 +20) |
231
+ | `host` | `127.0.0.1` | 网关监听地址;反代不在本机时才需要放开 |
232
+ | `targetPort` | (自动) | 本机 DSH Web UI 端口。留空即可:网关自动发现宿主实际监听端口(桌面端构建的端口可配置) |
233
+ | `rateLimit` | `120` | 未配对请求的每分钟上限(按真实客户端 IP) |
234
+ | `trustedProxies` | 空 | 逗号分隔 IP;反代不在本机时必填 |
235
+ | `vapidSubject` | `mailto:admin@localhost` | 推送联系人。**iOS 必须改成真实邮箱或 https 网址**,否则 Apple 拒发 |
236
+ | `lang` | `auto` | 网关页面、推送开关卡片与通知文案的语言。`auto` 跟随浏览器的 `Accept-Language`(认不出就用中文),`zh`/`en` 写死 |
237
+ | `pushTurnEnd` | 关 | 「回合结束」也推一条。默认不推——回合结束不代表需要你。等授权/等回答的通知不受影响,永远推 |
238
+ | `pushEvents` | `agent/turn-stopping` | 「回合结束」算哪些事件,逗号分隔;只在 `pushTurnEnd` 开时有意义 |
239
+ | `pushDebounceMs` | `15000` | 两条自动推送的最小间隔;等授权/等回答的通知不受压制 |
240
+ | `pushSummary` | 关 | 通知带上本回合的最终回复(只取正文,不含思考过程;截 120 字)和提问原文 |
241
+ | `pushTool` | 开 | 设 `false` 关掉模型可调用的 `push_notify` 工具 |
242
+ | `serverName` | 电脑名 | **设备名称**(≤ 40 字),两种角色共用:主服务端是子客户端分组标题里的显示名;子客户端是配对时注册的名字;与服务端双向自动同步 |
243
+ | `idleHours` | `48` | 远程会话的闲置休眠时长(小时),范围 (0, 8760] |
244
+ | `autoShareNewSessions` | 关 | 服务端新建的会话自动开启远程 |
245
+ | `serverUrl`(子客户端) | 空 | 服务端网关地址,按上文规则校验 |
246
+ | `deviceToken`(子客户端) | 空 | 配对拿到的设备令牌;密钥字段,处处打码 |
247
+ | `turnFoldDesktop` | 关 | 回合过程折叠在任意宽度生效(默认仅手机宽度) |
248
+ | `keyboardLiftRatio` / `keyboardLiftMaxPx` / `keyboardSafetyPadPx` | `0.42` / `400` / `15` | 软键盘抬升校准,给浏览器拿不到键盘高度的环境用(见已知问题一节) |
249
+ | `maxUploadBytes` | 20 MB | 附件上传上限 |
250
+
251
+ 旧的环境变量继续可用且优先级最高:`LAN_GATE_PORT`、`LAN_GATE_HOST`、`LAN_GATE_TARGET_PORT`、`LAN_GATE_RATE_LIMIT`、`LAN_GATE_TRUSTED_PROXIES`、`LAN_GATE_VAPID_SUBJECT`、`LAN_GATE_LANG`、`DSH_PUSH_TURN_END`、`DSH_PUSH_EVENTS`、`DSH_PUSH_DEBOUNCE_MS`、`DSH_PUSH_SUMMARY`、`DSH_PUSH_TOOL`、`DSH_PUSH_LANG`(通知文案覆盖;不设时由 `lang` 决定)、`DSH_PUSH_APPROVAL_GRACE_MS`(「等授权」推送前的等待窗口,给更快的自动审批插件让路;默认 5000;它与仅文件层的 `pushApprovalGraceMs` 键不在设置面板里)。`LAN_GATE_RELAY_SECRET` **不可**外部指定:插件每次加载现生成、自己交给网关。
252
+
253
+ 上传上限、键盘校准、回合折叠这几个旋钮与 1.x 行为一致——只是从「插件行 YAML」搬到了「设置页」(手写插件行 YAML 也照样有效)。
229
254
 
230
255
  ---
231
256
 
@@ -240,56 +265,99 @@ open http://127.0.0.1:3088/lan-gate/admin
240
265
  | 某个工具在等你授权 | 「DSH 等你授权」,带工具名 |
241
266
  | 模型调用 `ask_user_question` 在等你回答 | 「DSH 等你回答」 |
242
267
 
243
- 这两类不看会话层级——子代理自己卡在授权上,照样喊你,因为等的还是你。也**不受
244
- `DSH_PUSH_DEBOUNCE_MS` 压制**:「有操作等你点头」是最不能被吞掉的一条。
268
+ 这两类不看会话层级——子代理自己卡在授权上,照样喊你,因为等的还是你。也**不受最小间隔压制**:「有操作等你点头」是最不能被吞掉的一条。2.0 起桌面端 App 里跑的会话同样覆盖:钩子挂在服务端侧,会话在哪跑都一样。
245
269
 
246
- **有机器答复者时的时机**:审批事件的顺序是「先记 asked → 问答复者 → 记 decided」,
247
- 所以推送并不是一见到 asked 就发,而是等 `DSH_PUSH_APPROVAL_GRACE_MS`(默认 5 秒)
248
- ——这段时间内被答复掉的就不推。这个窗口原来是 1.5 秒,按「答复者都在同一个 tick
249
- 内结算」设计的;那对同步答复者成立,但对模型答复者不成立(实测平均 2.4 秒),
250
- 结果是自动通过的请求照样推了一条「等你授权」,通知到了、框却从来没出现。
251
- 换了更慢的判定模型就把这个值调大。
252
-
253
- 策略自动放行的授权不会打扰你:请求发起后先等 1.5 秒,配对的「已决定」到了就取消,
254
- 只有真正悬着没人管的才推。
270
+ **有机器答复者时的时机**:审批事件的顺序是「先记 asked → 问答复者 → 记 decided」,所以推送并不是一见到 asked 就发,而是等 `DSH_PUSH_APPROVAL_GRACE_MS`(默认 5 秒)——这段时间内被答复掉的就不推。换了更慢的模型答复者就把这个值调大(实测平均 2.4 秒)。策略自动放行的授权不会打扰你。
255
271
 
256
272
  **二、模型自己决定的**
257
273
 
258
- `push_notify` 工具,模型在这些时候该调:你明确要求做完通知、需要你介入才能继续、
259
- 出现你大概率想立刻知道的意外。不该调的场景(常规回合结束、进度汇报、它自己能推进
260
- 的事)同样写在工具描述里——只写前者会让它每回合都调。会话开始还会注入一段同源的
261
- 上下文强化,和工具描述共用一个常量,不会各改各的。
262
-
263
- **默认不会响的**
274
+ `push_notify` 工具,模型在这些时候该调:你明确要求做完通知、需要你介入才能继续、出现你大概率想立刻知道的意外。不该调的场景同样写在工具描述里,同一段文字还作为会话上下文注入,两处共用同一个常量、不会各改各的。
264
275
 
265
- - **普通跑完一轮不推**(1.0.3 起的行为变更,此前每回合都推)。干完活本身不等于
266
- 需要你。想要旧行为设 `DSH_PUSH_TURN_END=1`。
267
- - **子代理跑完永远不推**,无论上面那个开关。
276
+ **默认不会响的**:普通跑完一轮不推(想要旧行为设 `pushTurnEnd` 或 `DSH_PUSH_TURN_END=1`);子代理跑完永远不推,无论上面那个开关。
268
277
 
269
- **通知里写什么**:默认只有标题,不带对话内容。开 `DSH_PUSH_SUMMARY=1` 才带这一轮
270
- 的最终回复——只取正文,不含思考过程;这一轮没说话就退回「最后执行了 xx 工具」,不拿思考内容凑数。
271
- 推送 payload 是 aes128gcm 端到端加密的。
278
+ **通知里写什么**:默认只有标题。开 `pushSummary` 才带这一轮的最终回复——只取正文,不含思考过程;这一轮没说话就退回「最后执行了 xx 工具」。推送 payload 是 aes128gcm 端到端加密的。
272
279
 
273
280
  ---
274
281
 
275
282
  ## 功能
276
283
 
284
+ **主服务端(手机访问,1.x 的延续)**
285
+
277
286
  - 会话列表主屏 + 独立会话页两级页面栈,横向推入推出
278
287
  - 主屏插件入口 chips,按已装插件自动出现,显隐可自定义
279
288
  - composer 重排:控件图标化,权限/模型菜单变成底部 sheet
280
- - 会话信息卡:六格统计 + 导出日志 / 重命名 / Fork / 归档
281
- - 同一回合的推理与工具调用默认折叠成一条「过程 · N 步」
282
- - 手势:左边缘右滑返回、底部 sheet 下滑关闭;安卓系统返回手势接管为「先关弹层 → 退回列表 → 退出应用」,不再一按就退出 PWA
283
- - 手机本地附件上传:落到会话工作目录 `.dsh-uploads/`,输入框追加 `@` 引用,发不发你说了算
284
- - 分享图:信息卡一键把会话导出成一张 PNG 长图(全部对话或最近 3/5/10 轮,按真提问计轮),超长内容保尾去头并在首部标注、长代码行/宽表格不横向裁剪、助手正文按 markdown 渲染,走系统分享面板,老浏览器退化为多张下载
289
+ - 会话信息卡:六格统计 + 导出日志 / 重命名 / Fork / 归档;分享图一键导出 PNG 长图;2.0 起多一行「远程访问」开关
290
+ - 回合过程折叠、左缘右滑返回、安卓返回手势接管、手机本地附件上传
285
291
  - 配对码换长期设备令牌,认令牌不认 IP,可随时吊销
286
- - 管理面(生成配对码 / 管理设备 / 触发推送)只认本机直连,经反代一律 403
287
- - 真 PWA:manifest + service worker,可装到主屏、可离线打开
288
- - 真 Web Push:VAPID + aes128gcm,通知默认不带对话正文;默认只在等授权/等回答时响,回合结束不再打扰(见上)
289
- - `push_notify` 工具:模型可在关键节点自己推一条,带限流
290
- - 「内测声明」弹窗注入「不再弹出」可选项:远程访问每次刷新都会重弹声明,点一次后本设备记住选择、以后自动关闭
292
+ - 真 PWA + 真 Web Push(VAPID + aes128gcm),默认只在等授权/等回答时响
293
+
294
+ **主服务端(2.0 新增)**
295
+
296
+ - 会话共享:四个开关入口(含手机信息卡片)、闲置休眠、新建自动共享、子会话跟随
297
+ - 以上全部在 DSH 跑**桌面端 App** 时同样成立:网关自动转发到桌面端后台的实际端口,桌面端会话的等授权/等回答照推手机
298
+
299
+ **桌面应用端(2.0 新增)**
300
+
301
+ - 侧边栏远程分组(「服务端名 · 工作区名」),远程会话与本地会话同等使用:完整历史、实时进度、发消息、取消、排队调整、审批与提问应答、模型选择、文件树、改动列表与改动摘要 / diff、目标、斜杠命令、预设切换、子智能体提问/中断、附件与 @ 引用、开在服务端的终端
302
+ - 远程会话可附加**非图片文件**:上传请求由子客户端在本机后台接住、经中继流式转到服务端会话;上限 100 MiB 是中继自定的(本机上传无此限),超限在子客户端本地即提示;慢速链路受 300 秒超时约束
303
+ - 远程会话里的模型下拉框显示**服务端**的模型目录(分组名「服务端名 · 分组名」),本机会话只显示本机模型;中继离线时保留上次拉到的服务端分组;视觉路由等本机设置里不会出现服务端分组。已知边界:服务端新建且未发过消息的会话,下拉框按本机默认模型显示
304
+ - 远程分组里新建的会话跑在服务端对应工作区、自动开启远程;远程会话可改名、归档、置顶、分叉,也可从子客户端关闭其远程(带确认)
305
+ - 远程会话标题行连接图标(在线 / 离线 / 版本有差异);会话被休眠或服务端关闭时显示「远程已关闭」及原因;服务端离线时分组变灰、输入禁用,后台自动退避重连
306
+ - 审批/提问先到先得:服务端自己的界面先答了,子客户端自动同步成「已处理」
307
+
308
+ 深度说明:[界面](docs/interface.md) · [公网接入 + 中继协议](docs/remote-access.md)
309
+
310
+ ---
311
+
312
+ ## 版本容错
291
313
 
292
- 深度说明:[界面](docs/interface.md) · [公网接入](docs/remote-access.md)
314
+ 三层,从严到宽:
315
+
316
+ 1. **中继协议版本**必须一致——实际操作上,**两端 zen-remote 需同为 2.0.x**。服务端若还是 1.x,它的网关不认子客户端的 Bearer 令牌,请求会撞上通用配对墙,子客户端报「令牌已失效(或服务端 zen-remote 低于 2.0.0)」。DSH 版本可以不同。
317
+ 2. **DSH 接口指纹**决定其余:两端各自对中继会用到的几组远程接口生成规范化指纹,握手时比对。全部一致即完全兼容,版本号不同也无关;有差异照常连接,远程分组标注「(版本有差异)」,设置页诊断区列出差异组,只有受影响的面板降级。
318
+ 3. **运行时降级**:某次转发调用因参数或结果校验失败,只有那个面板显示「与服务端版本不兼容」并记入诊断区,其余功能继续可用。
319
+
320
+ ---
321
+
322
+ ## 安全模型
323
+
324
+ 一台已配对设备被信任到什么程度,明说:
325
+
326
+ - **已配对的桌面应用端被视为可信设备。** 远程会话里的终端是以**服务端用户**身份运行的 shell,不受智能体沙箱与审批限制。文件预览接口(`workspaceFiles/read` 等)**不**限制在会话目录内——服务端进程读得到的文件都读得到。已配对设备可以在服务端**任意**工作区新建会话(不受「开启远程」限制;新建的会话自动开启远程)。
327
+ - **「只有开启远程的会话可见」约束的是会话数据**:未开启远程的会话,其列表、历史、实时进度、审批/提问事件都被中继拒绝或过滤;@ 引用候选**只列已共享会话**;发消息、改写排队消息、斜杠命令里内嵌的 `dsh-session:` 会话引用一旦指向未开启远程的会话,整个调用被服务端拒绝。它不是围住整台机器的沙箱。
328
+ - **管理操作只在服务端本机**:生成配对码、改角色、吊销只能在跑服务端那台机器的设置页做——经网关进来的请求可以查看状态,但变更动作被拒——唯一例外是开关会话的远程:Web 应用端(手机)可以开关,手机信息卡片里的「远程访问」开关就走这条;桌面应用端根本够不到管理路由。
329
+
330
+ ---
331
+
332
+ ## 已知限制
333
+
334
+ - 同时开多个 DSH 窗口(包括用浏览器打开桌面端的后台页面)时,子客户端对「当前打开的是哪个会话」可能判断不准:关闭远程的会话隐藏时机可能不对,极端情况下正打开着的会话会被当成后台会话、被踢回首页。
335
+ - 换到另一台服务端后再换回原来那台,或服务端删除某工作区后又以同一 id 重建:相关远程分组要**重载页面**才会再出现(DSH 侧边栏对移除过的工作区 id,在页面生命周期内不再接受)。
336
+ - 远程会话里「导出会话」已禁用(菜单项隐藏,后台对虚拟会话 id 一律拒绝);改动面板的**摘要与 diff 可以看**(经中继转发),但「打开 / 在访达中显示 / 在应用中打开」这类会在服务端机器上弹窗的入口在远程会话里隐藏(交付物卡片同理)。
337
+ - @ 引用候选由宿主**先截前 50 条再按共享过滤**:服务端会话很多时,已共享会话可能不出现在候选列表里(不影响引用检查本身——直接写出的引用仍会被逐条校验)。
338
+ - 第三方插件自己的非标准接口不转发——它们的面板在远程会话里降级或隐藏。插件的**工具**跑在服务端,不受影响;受影响的是插件在界面上带着会话 id 调它自己后台接口的部分(dsh-better-sidebar 就是典型,已按下一条处理)。
339
+ - 装了 [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) 时,远程会话里它的文件浏览、编辑器、git、任务、底部工作台等界面会被隐藏——它们读写的是子客户端**本机**的文件;又因为它接管了宿主的「文件」标签,远程会话里暂时没有侧栏文件浏览;聊天或交付物里的文件链接在远程会话里会打开一个空的右栏。
340
+ - 两端 zen-remote 需同为 **2.0.x**(中继协议版本一致);DSH 版本可以不同,接口指纹有差异时远程分组标注「(版本有差异)」。
341
+
342
+ ---
343
+
344
+ ## 已知问题
345
+
346
+ **iOS 26.x 独立 PWA 视口缩水**:加到主屏后视口底部会少掉一条状态栏高度,普通 Safari 标签页正常。这是 iOS 系统缺陷,缺掉的区域在文档之外,CSS 够不着;本插件做了三层缓解(浅色 manifest 背景 + 安全区补偿 + 强制重排),能减轻但不保证复原。彻底恢复只能整个 App 退出重开。
347
+
348
+ **个别环境软键盘对浏览器完全不可见,输入框抬升靠估算兜底**:部分组合里系统不把键盘高度告知页面(visualViewport、VirtualKeyboard API 一并失效,均已实测排除)。插件聚焦后探测约 1.2 秒,判定「键盘不可见」就按估计高度抬升输入框。抬升高度差得明显的话,在设置页里调 `keyboardLiftRatio` / `keyboardLiftMaxPx` / `keyboardSafetyPadPx`。正常环境完全不走这条路径。
349
+
350
+ **经反代访问时设置页打不开**(插件配置列表空白、模型卡片报「settings are unavailable in this browser」):DSH 官方设计是设置类 RPC 只对回环连接开放,远程浏览器的设置镜像初始即 `unavailable`。绕法:回跑 DSH 的那台机器用本机浏览器改。经网关打开的插件设置页同样受此限制。与本插件无关。
351
+
352
+ ---
353
+
354
+ ## 权限与数据
355
+
356
+ - **网络**:网关只监听本机(默认 `127.0.0.1:3088`),对外暴露完全由你的反代/隧道决定;推送经浏览器推送服务商中转(aes128gcm 端到端加密);桌面应用端与主服务端之间的中继也走这条网关,授权在服务端逐请求复核。插件自身不向任何第三方上报数据。
357
+ - **文件**:附件上传只写入当前会话工作目录下的 `.dsh-uploads/`;配对状态在 `~/.dsh/lan-gate-state.json`,配置在 `~/.dsh/lan-gate.config.json`,共享表在 `~/.dsh/zen-remote-shares.json`,服务端 id 在 `~/.dsh/zen-remote-server.json`。
358
+ - **凭据**:不收集、不存储任何账号密码;设备身份是本插件自己签发的随机令牌(Web 应用端为 HttpOnly Cookie,桌面应用端为密钥字段),外加一枚每次加载现生成的中继共享密钥,不出这台机器对。
359
+
360
+ 排障:运行日志在 `~/.dsh/logs/web.log`(网关与推送的行带 `[dsh-zen-remote-*]` 前缀;桌面端构建打到 App 控制台)。安全问题请走 GitHub Security Advisories 私下报告,不要公开提 issue。
293
361
 
294
362
  ---
295
363
 
@@ -313,28 +381,6 @@ open http://127.0.0.1:3088/lan-gate/admin
313
381
 
314
382
  ---
315
383
 
316
- ## 已知问题
317
-
318
- **iOS 26.x 独立 PWA 视口缩水**:加到主屏后视口底部会少掉一条状态栏高度,普通 Safari 标签页正常。这是 iOS 系统缺陷,缺掉的区域在文档之外,CSS 够不着;本插件做了三层缓解(浅色 manifest 背景 + 安全区补偿 + 强制重排),能减轻但不保证复原。彻底恢复只能整个 App 退出重开。
319
-
320
- **个别环境软键盘对浏览器完全不可见,输入框抬升靠估算兜底**:部分组合(实测过:某些第三方输入法 + Chrome;小米浏览器安装的 PWA 壳)里,键盘弹出/收起时系统不把键盘高度告知页面——视口不变、无任何事件(visualViewport、VirtualKeyboard API 一并失效,均已实测排除)。插件的兜底是:聚焦后探测约 1.2 秒,判定「键盘不可见」就按估计高度抬升输入框(判定按浏览器记忆,之后聚焦即时抬升)。代价有两条:抬升高度是估算的,可能与实际键盘有几十像素出入;键盘收起同样无信号,输入框要等你点击或滑动输入框以外的区域才回落。正常环境完全不走这条路径,不受影响。抬升高度差得明显的话不用改代码,插件行的 `keyboardLiftRatio` / `keyboardLiftMaxPx` / `keyboardSafetyPadPx` 三个值可以照着自己的机器调,见上面「配置」一节。
321
-
322
- **经反代访问时设置页打不开(插件配置列表空白、模型卡片报「settings are unavailable in this browser」)**:直连 `127.0.0.1:3080/3088` 正常。
323
-
324
- 根因是 DSH 官方的设计,不在网关:设置类 RPC **只对回环连接开放**。客户端按 `location.hostname` 判定(`dsh-client-connection` 的 `isLoopback`),非回环时 `dsh-client-ui-settings` 把持久化降级为 `memory`,设置镜像初始状态就是 `unavailable`——官方源码注释原话是「remote browsers remain process-local because settings RPCs are loopback-only」。所有依赖这个镜像的卡片(模型、插件配置)因此一起空白,与本插件、与 service worker 缓存都无关(2026-08-20 真机 USB 调试 + 本机对照实测)。
325
-
326
- 绕法:要改配置就回跑 DSH 的那台机器上用本机浏览器改,配置存在后端,改完手机侧其它功能不受影响。想让远程也能改设置,得由上游放开这条限制。
327
-
328
- ---
329
-
330
- ## 权限与数据
331
-
332
- - **网络**:网关只监听本机(默认 `127.0.0.1:3088`),对外暴露完全由你的反代/隧道决定;推送经浏览器推送服务商中转(内容 aes128gcm 端到端加密,服务商读不到);插件自身不向任何第三方上报数据。
333
- - **文件**:附件上传只写入当前会话工作目录下的 `.dsh-uploads/`;配对状态与配置存在 `~/.dsh/lan-gate-state.json` / `lan-gate.config.json`。
334
- - **凭据**:不收集、不存储任何账号密码;设备身份是本插件自己签发的随机令牌(HttpOnly Cookie)。
335
-
336
- 排障:运行日志在 `~/.dsh/logs/web.log`(网关与推送的行带 `[dsh-zen-remote-*]` 前缀);手机端界面自检可用调试徽章(首页顶栏连点 5 下开关)。安全问题请走 GitHub Security Advisories 私下报告,不要公开提 issue。
337
-
338
384
  ## 上游致谢
339
385
 
340
386
  本插件的界面层衍生自 [mexiaosqwq/dsh-web-mobile](https://github.com/mexiaosqwq/dsh-web-mobile),通道层衍生自 [zylzyqzz/dsh-mobile-pwa](https://github.com/zylzyqzz/dsh-mobile-pwa)(其自身衍生自 [Bernardxu123/dsh-mobile-gate](https://github.com/Bernardxu123/dsh-mobile-gate)),均为 MIT,原始版权行保留在 [LICENSE](LICENSE)。
package/assets/home.png CHANGED
Binary file
package/assets/info.png CHANGED
Binary file
Binary file
Binary file
package/assets/sheet.png CHANGED
Binary file