dsh-zen-remote 2.3.2 → 2.3.4

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 (73) hide show
  1. package/README.md +178 -282
  2. package/README.zh-CN.md +179 -283
  3. package/docs/interface.md +1 -1
  4. package/docs/plugin-bridge.md +14 -7
  5. package/docs/remote-access.en.md +8 -8
  6. package/docs/remote-access.md +10 -10
  7. package/lib/adapters/dsh-better-sidebar/index.js +30 -10
  8. package/lib/adapters/dsh-better-sidebar/index.js.map +1 -1
  9. package/lib/adapters/dsh-better-sidebar/surface.js +79 -0
  10. package/lib/adapters/dsh-better-sidebar/surface.js.map +1 -0
  11. package/lib/client.js +1 -1
  12. package/lib/index.js +103 -10
  13. package/lib/index.js.map +1 -1
  14. package/lib/intercept.js +23 -1
  15. package/lib/intercept.js.map +1 -1
  16. package/lib/merge-streams.js +29 -2
  17. package/lib/merge-streams.js.map +1 -1
  18. package/lib/plugin-adapter-core.js +69 -13
  19. package/lib/plugin-adapter-core.js.map +1 -1
  20. package/lib/plugin-bridge.js +179 -12
  21. package/lib/plugin-bridge.js.map +1 -1
  22. package/lib/plugin-route-intercept.js +631 -79
  23. package/lib/plugin-route-intercept.js.map +1 -1
  24. package/lib/plugin-tunnel.js +84 -12
  25. package/lib/plugin-tunnel.js.map +1 -1
  26. package/lib/relay-server.js +65 -47
  27. package/lib/relay-server.js.map +1 -1
  28. package/lib/types/adapters/dsh-better-sidebar/index.d.ts +1 -1
  29. package/lib/types/adapters/dsh-better-sidebar/index.d.ts.map +1 -1
  30. package/lib/types/adapters/dsh-better-sidebar/surface.d.ts +23 -0
  31. package/lib/types/adapters/dsh-better-sidebar/surface.d.ts.map +1 -0
  32. package/lib/types/adapters/types.d.ts +14 -3
  33. package/lib/types/adapters/types.d.ts.map +1 -1
  34. package/lib/types/index.d.ts +2 -2
  35. package/lib/types/index.d.ts.map +1 -1
  36. package/lib/types/intercept.d.ts +19 -0
  37. package/lib/types/intercept.d.ts.map +1 -1
  38. package/lib/types/merge-streams.d.ts +12 -0
  39. package/lib/types/merge-streams.d.ts.map +1 -1
  40. package/lib/types/plugin-adapter-core.d.ts +24 -11
  41. package/lib/types/plugin-adapter-core.d.ts.map +1 -1
  42. package/lib/types/plugin-bridge.d.ts +93 -5
  43. package/lib/types/plugin-bridge.d.ts.map +1 -1
  44. package/lib/types/plugin-route-intercept.d.ts +52 -3
  45. package/lib/types/plugin-route-intercept.d.ts.map +1 -1
  46. package/lib/types/plugin-tunnel.d.ts +27 -3
  47. package/lib/types/plugin-tunnel.d.ts.map +1 -1
  48. package/lib/types/relay-server.d.ts +2 -25
  49. package/lib/types/relay-server.d.ts.map +1 -1
  50. package/lib/types/upload-limits.d.ts +67 -0
  51. package/lib/types/upload-limits.d.ts.map +1 -0
  52. package/lib/types/wizard/proxy-snippets.d.ts +4 -3
  53. package/lib/types/wizard/proxy-snippets.d.ts.map +1 -1
  54. package/lib/upload-limits.js +67 -0
  55. package/lib/upload-limits.js.map +1 -0
  56. package/lib/wizard/proxy-snippets.js +5 -4
  57. package/lib/wizard/proxy-snippets.js.map +1 -1
  58. package/package.json +14 -14
  59. package/scripts/check-upload-limits.mjs +167 -0
  60. package/src/adapters/dsh-better-sidebar/index.ts +30 -11
  61. package/src/adapters/dsh-better-sidebar/surface.ts +90 -0
  62. package/src/adapters/types.ts +14 -3
  63. package/src/client-data/wizard.ts +1 -1
  64. package/src/index.ts +99 -12
  65. package/src/intercept.ts +47 -1
  66. package/src/merge-streams.ts +41 -1
  67. package/src/plugin-adapter-core.ts +69 -14
  68. package/src/plugin-bridge.ts +228 -14
  69. package/src/plugin-route-intercept.ts +677 -82
  70. package/src/plugin-tunnel.ts +95 -13
  71. package/src/relay-server.ts +60 -48
  72. package/src/upload-limits.ts +69 -0
  73. package/src/wizard/proxy-snippets.ts +5 -4
package/README.zh-CN.md CHANGED
@@ -28,388 +28,284 @@
28
28
 
29
29
  ---
30
30
 
31
- ## 一个插件、两种角色
32
-
33
- 同一个包、同一行插件。跑哪一半由 DSH「插件」页设置区块里的 `role` 决定:
34
-
35
- | | **主服务端**(`role: host`,默认) | **子客户端**(`role: client`) |
36
- | --- | --- | --- |
37
- | 机器 | 跑会话的那台 | 另一台装了 DSH 桌面端的电脑 |
38
- | 加载什么 | 手机界面、网关(子进程)、Web 推送、会话共享、中继服务端 | 中继客户端 + 侧边栏与会话页的远程部件 |
39
- | 不加载什么 | — | 不起网关、不注册推送、手机界面不生效——不和主服务端抢端口 |
40
-
41
- 已配对的**桌面应用端**只看得到服务端开启了远程的会话,在本地工作区后面按「服务端名 · 工作区名」分组。服务端的工作区只有其中**至少一个会话开启了远程**才会出现——没动过的工作区不会显示成空分组;已经显示过的分组里会话全部关闭远程后,空分组会留到刷新页面为止。子客户端本地的会话完全不受影响。
31
+ ## 目录
32
+
33
+ 1. [安装](#1-安装)
34
+ 2. [快速上手(一体化配置与配对向导)](#2-快速上手一体化配置与配对向导)
35
+ - [场景 A:配置主服务端并配对手机 / 电脑](#场景-a作为主服务端开放手机与另一台电脑连入)
36
+ - [场景 B:配置另一台电脑作为桌面客户端](#场景-b作为桌面客户端连接远端服务端)
37
+ 3. [日常使用:会话共享与跨端协作](#3-日常使用会话共享与跨端协作)
38
+ 4. [锁屏推送:通知什么时候会响](#4-锁屏推送通知什么时候会响)
39
+ 5. [功能一览](#5-功能一览)
40
+ 6. [进阶配置与字段参考](#6-进阶配置与字段参考)
41
+ 7. [架构、安全模型与已知说明](#7-架构安全模型与已知说明)
42
42
 
43
43
  ---
44
44
 
45
- ## 远程连接
46
-
47
- **适合什么场景**:一台电脑常开、跑会话(主服务端),另一台电脑的 DSH 桌面端(子客户端)远程查看、接着干活——历史、实时进度、发消息、审批、终端都能用,所有执行都发生在主服务端。手机、平板走的是另一条路:浏览器经网关访问主服务端(Web 应用端),见[配置公网访问](#配置公网访问主服务端)。
48
-
49
- **怎么用**,四步:
50
-
51
- 1. **配对**:主服务端设置页生成「桌面应用端」配对码,子客户端设置页填服务端地址和配对码。逐步说明见下面的[设置页配置引导](#设置页配置引导)。
52
- 2. **开启远程**:远程按会话逐个开。主服务端在会话的「…」菜单里点「开启远程」(也可以用标题行的地球图标、设置页的共享列表,见[会话共享](#会话共享))。
53
-
54
- ![主服务端在会话的「…」菜单里开启远程](docs/images/host-share-menu.png)
55
-
56
- 3. **在子客户端使用**:侧边栏本地工作区后面出现「服务端设备名称 · 工作区名」分组,点开就像本地会话一样用;标题行的地球图标显示连接状态(在线 / 离线 / 版本有差异)。服务端上没有开启远程会话的工作区不会出现。
45
+ ## 1. 安装
57
46
 
58
- ![子客户端侧边栏里的「书房 · Desktop」分组,标题行是远程连接状态图标](docs/images/client-sidebar.png)
47
+ ### 界面一键安装(推荐,桌面端 App 与 Web 端通用)
59
48
 
60
- 4. **关闭与恢复**:服务端关闭远程、会话闲置休眠(默认 48 小时无动静)、或在子客户端自己关闭时,正打开的会话页会显示「远程已关闭」横幅并禁用输入,切到别的会话后它从列表里隐藏;重新开启远程后自动回来。断线期间页面保持原样,连上后自动续上。
49
+ 打开 DSH 左侧**「插件」**页 → 点击**「添加插件」**→ 输入 `dsh-zen-remote` → 安装完成后点击**「立即启用」**,然后重启 DSH 桌面端 App(或 `dsh web`)。
61
50
 
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` 的主服务端,手机访问也指向这里):
51
+ ### Web 服务命令行安装(无头服务器 `dsh web`)
79
52
 
80
53
  ```sh
81
54
  dsh plugin --profile web add dsh-zen-remote
82
55
  # 然后重启 dsh web
83
56
  ```
84
57
 
85
- > **开着 HMR 升级插件:** 先 remove 再 install(`dsh plugin --profile <name> remove dsh-zen-remote` 后再 `add dsh-zen-remote`,或在「插件」页先卸载再添加);直接在运行中的旧版上覆盖安装不会被干净地接管。
86
-
87
- > **DSH 0.2.1-alpha.3 起:** `dsh --profile <name> --from-default-profile <模板>` 会用模板新副本**覆盖**已有的自定义 profile,删掉其中的 `dependencies`(包括 dsh-zen-remote)和用户 `cordis.patch.yml`。执行前先备份 `~/.dsh/profiles/<name>`(如 `cp -a ~/.dsh/profiles/<name> ~/.dsh/profiles/<name>.bak-$(date +%Y%m%d%H%M%S)`),之后重新装插件。`~/.dsh/*.json` 里的配对/共享状态在 profile 之外,不受影响。
58
+ > `dsh plugin` 必须带 `--profile` 指定目标 profile。桌面端的 `desktop` profile 由 App 独占管理,命令行会拒绝 `--profile desktop`,桌面端请直接使用上面的界面安装。
88
59
 
89
- `dsh plugin` 必须用 `--profile` 指明装进哪个 profile,不带会直接报错。桌面端的 `desktop` profile 由 App 独占管理,命令行会拒绝 `--profile desktop`,所以桌面端只能用上面的界面安装。
60
+ <details>
61
+ <summary>手动编辑 Profile / 本地开发(<code>link:</code> 安装)与升级、卸载说明</summary>
90
62
 
91
- **手工改 profile**(本地开发、`link:` 装法):把依赖写进 profile 的 `package.json`(桌面端在 `~/.dsh/profiles/desktop`,Web 端在 `~/.dsh/profiles/web`),装完重启:
63
+ **手工改 profile**(本地开发、`link:` 装法):把依赖写进 profile 的 `package.json`(桌面端在 `~/.dsh/profiles/desktop`,Web 端在 `~/.dsh/profiles/web`),并确保 `"dsh-zen-remote"` 已加入同文件 `dsh.profile.bundles` 数组末尾:
92
64
 
93
65
  ```jsonc
94
66
  // ~/.dsh/profiles/desktop/package.json
95
67
  {
96
68
  "dependencies": {
97
- "dsh-zen-remote": "^2.3.2" // 本地开发换成 "link:/path/to/dsh-zen-remote"
69
+ "dsh-zen-remote": "^2.3.4" // 本地开发换成 "link:/path/to/dsh-zen-remote"
98
70
  }
99
71
  }
100
72
  ```
101
73
 
102
74
  ```sh
103
75
  cd ~/.dsh/profiles/desktop && pnpm install
104
- # 然后重启 DSH 桌面端 App(Web 端重启 dsh web)
76
+ # 若使用 link: 本地仓库,还需在仓库目录内执行一次 npm install --omit=dev 安装网关子进程的 web-push 依赖
105
77
  ```
106
78
 
107
- `pnpm install` 只装包、不启用。还要到「插件」页打开它的开关,或者自己把 `"dsh-zen-remote"` 追加到同一个文件 `dsh.profile.bundles` 数组的末尾(数组里原有的项别动)。界面安装和 `dsh plugin` 会自动写这一项,手工装法要自己补。
108
-
109
- 无论哪种装法,组合层都只有**一行**。2.0.0 起挂载层只剩 `dsh-zen-remote` 一行,主入口按角色自己加载网关与推送子插件。不要再把旧的 `dsh-zen-remote-gateway` / `dsh-zen-remote-push` 两行加回来:loader 会警告后跳过;真挂两行网关就是两个网关进程抢 3088 端口。
110
-
111
- <details>
112
- <summary>静态挂载 / 本地开发</summary>
113
-
114
- 把 [`cordis.patch.yml.example`](cordis.patch.yml.example) 抄进 profile 的 `cordis.patch.yml`、换成绝对路径即可——同样只有一行,附带的 `config:` 示例列了可写的行配置。用界面或 `dsh plugin` 正常安装的不需要它。
79
+ **升级与迁移注意**:
80
+ - **开着 HMR 升级插件**:先卸载再添加(`dsh plugin --profile <name> remove dsh-zen-remote` 后再 `add`),直接在运行中的旧版上覆盖安装不会被干净接管。
81
+ - **DSH 0.2.1-alpha.3+ 模板重置**:`dsh --profile <name> --from-default-profile <模板>` 会用新副本覆盖自定义 profile,执行前请先备份 `~/.dsh/profiles/<name>`。
82
+ - **从 1.x 升级**:2.0 起挂载层合并为 `dsh-zen-remote` 单行,主入口按角色自动加载网关与推送,请删掉 `cordis.patch.yml` 里旧的 `dsh-zen-remote-gateway` / `dsh-zen-remote-push` / `dsh-push.mjs` 行。原有 `~/.dsh/lan-gate.config.json` 与已配对设备全部自动保留,无需重配。
83
+ - **彻底卸载**:在「插件」页点「卸载」后重启。若需连同配对与共享记录一并清空,删除 `~/.dsh/lan-gate-state.json`、`~/.dsh/lan-gate.config.json`、`~/.dsh/zen-remote-shares.json` 与 `~/.dsh/zen-remote-server.json`。
115
84
 
116
85
  </details>
117
86
 
118
- 卸载:在「插件」页打开 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;删了之后已配对的子客户端会把它当成一台新服务端)。
119
-
120
87
  ---
121
88
 
122
- ## 从 1.x 升级
123
-
124
- 一次性的事实,按踩到的概率排序:
125
-
126
- - **三行合并为一行。** profile 补丁里残留的 `dsh-zen-remote-gateway` / `dsh-zen-remote-push` 旧行会被 loader 警告后跳过。原来写在**那两行上**的 `config:` 不再生效——搬到 `dsh-zen-remote` 行,或者直接在插件设置页里改。
127
- - **1.0 时代在 profile 补丁里手工 insert 过 `dsh-zen-remote/dsh-push.mjs` 那一行的,需要删掉**——主入口现在按角色自动加载推送子插件,残留的那一行会让推送加载两次。
128
- - **`~/.dsh/lan-gate.config.json` 继续生效。** 无需迁移,它的值会显示在设置页里。每个字段的优先级:环境变量 > 插件行设置 > `lan-gate.config.json` > 内置默认值。插件从不改写、也从不删除这个文件。
129
- - **已配对设备自动归为 Web 应用端**,继续照常用,不需要重新配对。
130
- - **管理页搬家了。** `/lan-gate/admin` 现在只剩一段指向插件设置页的说明(对非本机直连的请求仍然 403)。配对码、设备管理、测试推送、会话共享全部在 DSH「插件」页的 dsh-zen-remote 设置区块里。
131
- - 什么配置都不动的话,插件按主服务端起来,网关、推送、手机界面的行为与 1.1.x 完全一致。
132
-
133
- ---
89
+ ## 2. 快速上手(一体化配置与配对向导)
134
90
 
135
- ## 配置公网访问(主服务端)
91
+ 同一个插件包含两种运行角色:
92
+ - **作为服务端(主机,`role: host`)**:运行主力会话、启动本地安全网关与锁屏推送,允许手机浏览器/PWA 或另一台电脑连入。
93
+ - **作为桌面客户端(从机,`role: client`)**:不启动网关也不抢端口,连接到远端服务端,把服务端共享的会话直接挂载到本机侧边栏。
136
94
 
137
- 装完在本机 `127.0.0.1:3080` 就能用手机界面。要从外面访问:给自己的网关配一个终结 TLS 的反代,用 403 自检确认,再配对设备。nginx / Caddy / Cloudflare Tunnel / Lucky 的完整配置见 [docs/remote-access.md](docs/remote-access.md)。**不想手配?**服务端插件详情页有「让手机连上来」配置引导,三步走完下面的主流路线(托管 Caddy + DNS-01 证书、现有反代的片段生成与验证、全自动 Cloudflare Tunnel)。简版:
95
+ 打开 DSH **「插件」页 → 点击 `dsh-zen-remote`**,页面顶部就是**「远程访问与设备配对」**向导卡片:
138
96
 
139
- 1. 网关只监听 `127.0.0.1:3088`,由反代(nginx、Caddy、Cloudflare Tunnel、Lucky)终结 HTTPS 并转发。**自检**:用**手机流量**(别连家里 Wi-Fi)访问 `https://你的域名/lan-gate/admin`,正确结果是 **403**。能看到说明页就代表反代没带 `X-Forwarded-*` 头、公网请求被当成了本机用户——先修转发头再往下走。
140
- 2. 在**服务端这台机器**打开 DSH「插件」页,展开 dsh-zen-remote 设置区块,先选要配的设备角色,再点生成 8 位配对码(10 分钟有效、只能用一次)。
141
- 3. 手机打开你的 HTTPS 域名,在配对页输入配对码。配对成功即进入 DSH,身份存在长期 Cookie 里。浏览器菜单「添加到主屏幕」装成 App,同意通知权限,agent 干完活推到锁屏。
97
+ ![插件详情页顶部的「远程访问与设备配对」状态总览卡片](docs/images/wizard-overview.png)
142
98
 
143
99
  ---
144
100
 
145
- ## 配对设备
101
+ ### 场景 A:作为主服务端(开放手机与另一台电脑连入)
146
102
 
147
- 配对码生成时**绑定设备角色**,兑换时校验角色——拿错通道用码会被明确拒绝,码不被消耗,也不计入错码锁定。
103
+ 点击卡片上的**「开始配置向导」**(已配置过则点**「修改接入配置」**),三步完成:
148
104
 
149
- | | **Web 应用端** | **桌面应用端** |
150
- | --- | --- | --- |
151
- | 形态 | 手机、平板及各类浏览器,经网关访问 | 装了 DSH 桌面端、zen-remote 设为子客户端角色的计算机 |
152
- | 可见范围 | 服务端**全部**会话 | **仅**服务端已开启远程的会话(服务端强制) |
153
- | 配对方式 | 浏览器打开网关地址,在配对页输入配对码 | 在子客户端的插件设置页填服务端地址和配对码 |
154
-
155
- **桌面应用端配对**,逐步:
156
-
157
- 1. 在服务端的插件设置区块选「桌面应用端」,生成配对码。
158
- 2. 在另一台电脑的 DSH 桌面端里打开插件设置区块,角色切到**子客户端**,填入服务端地址和配对码。
159
- 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 会被拒绝,设备令牌不会明文过公网。
160
- 4. 配对成功后令牌存在插件行里(密钥字段),客户端自动连接。设置区块显示连接状态——已连接 / 离线 / 版本有差异 / 令牌已吊销——并可以解配。
161
-
162
- 服务端的设置区块可以改设备名、改设备角色(`set-role`)、单独或全部吊销。吊销立即生效:连接与推送订阅一并失效。桌面应用端够不到任何管理路由——网关只把它的请求转进中继前缀。
163
-
164
- **设备名称**:设置页「角色」卡片里的「设备名称」字段(配置项 `serverName`)两种角色共用:主服务端上它是其他设备侧边栏分组标题里的名字(默认取电脑名);子客户端上它是配对时注册到服务端的名字。名字双向自动同步:服务端改自己的名字,子客户端分组标题约 15 秒内跟随;子客户端保存新名字,服务端设备列表跟着更新;服务端在设备列表里给子客户端改名,子客户端约 30 秒内跟随。子客户端只在主动保存设置时推送自己的名字,其余时候以服务端记录为准;离线时改的名字如果进程在推送前退出,下次启动以服务端记录为准。名字 1–40 个字符。
105
+ #### 第 1 步:选择「作为服务端(主机)」与接入方式
165
106
 
166
- ---
167
-
168
- ## 会话共享
107
+ ![配置向导第 1 步:选择本机角色与服务端接入方式](docs/images/wizard-step1-role-path.png)
169
108
 
170
- 远程按会话逐个开启,有四个入口:
109
+ 根据你的网络环境四选一(切换接入方式时,向导会自动停止上一方式的托管进程并重置网关监听地址):
171
110
 
172
- 1. 侧边栏会话行的「…」菜单里的「开启远程 / 关闭远程」;
173
- 2. 会话页标题行的远程图标(点击开关、带二次确认;悬停显示剩余闲置时间;图标带小圆点表示此刻有桌面应用端在看);
174
- 3. 插件设置区块的「已开启远程的会话」列表,含一键全部关闭;
175
- 4. 手机(Web 应用端)会话页信息卡片里的「远程访问」开关。
111
+ | 接入方式 | 适合什么环境 | 向导自动为你做什么 |
112
+ | --- | --- | --- |
113
+ | **内置 Caddy(推荐)** | 有公网 IP(或已做路由器端口映射)+ 域名托管在 Cloudflare | 自动下载预编译的 `Caddy + caddy-dns/cloudflare`(中国大陆网络自动走镜像源加速),通过 DNS-01 自动申请并续期 HTTPS 证书;网关仅监听 `127.0.0.1:3088` |
114
+ | **使用已有反向代理** | 机器上已跑着 Nginx、Caddy 或 Lucky | 自动生成带 WebSocket 与 `X-Forwarded-*` 头的现成配置片段,支持一键将反代源 IP 写入受信任代理列表(手配完整说明见 [docs/remote-access.md](docs/remote-access.md)) |
115
+ | **Cloudflare Tunnel 内网穿透** | 没有公网 IP / 不想配路由器端口映射 + 域名托管在 Cloudflare | 自动下载 `cloudflared`(国内网络自动走镜像源加速),调用 Cloudflare API 自动创建或复用隧道、写入 DNS CNAME 记录并拉起隧道;网关仅监听 `127.0.0.1:3088` |
116
+ | **局域网 / Tailscale 直连** | 手机或另一台电脑在同一 Wi-Fi / 局域网,或共用 Tailscale 虚拟局域网 | 无需域名与公网证书;自动识别本机局域网 / Tailscale IPv4 地址并监听 `0.0.0.0:3088`,同时网关内置**私网源 IP 铁闸**,直接拒绝任何公网 IP 的未代理明文直连(`403 public-direct-forbidden`) |
176
117
 
177
- **闲置休眠**:开启远程的会话在 **48 小时**(可配,`idleHours`)内没有任何会话动静——回合开始或结束、发消息、审批或提问得到回答,不分服务端还是子客户端——就自动关闭远程。运行中的回合、等待审批/回答的会话永远不会被扫掉;只开窗口「看着」不算活跃。剩余时间悬停标题行图标可见,设置区块里逐条列出。
118
+ #### 第 2 步:填写连接参数
119
+ - **内置 Caddy / Cloudflare Tunnel**:填写公开访问域名(如 `dsh.example.com`)并粘贴 Cloudflare API 令牌(点击输入框旁的「如何获取 API 令牌?」可查看分步指引与权限表;域名尚未接入 Cloudflare 可点底部「域名尚未接入 Cloudflare?」查看迁入步骤)。
120
+ - **使用已有反向代理**:填写反代对外提供的 `https://` 地址,点击「查看 Nginx / Caddy / Lucky 配置片段」复制到你的反代中。
121
+ - **局域网 / Tailscale 直连**:向导会自动检测并预填 `http://<本机局域网或Tailscale IP>:3088`,确认即可。
178
122
 
179
- **新建会话**:`autoShareNewSessions` 打开时,服务端新建的会话自动开启远程;**经中继**新建的会话(子客户端远程分组里点的)一律自动开启,这条入口的安全性正建立在此之上。子智能体会话与分叉会话自动跟随父会话,不用逐个开启。
123
+ #### 第 3 步:连通验证与扫码 / 配对码配对
180
124
 
181
- **「关闭远程」意味着什么**:未开启远程的会话,其列表、历史、实时进度、审批/提问事件,全部在中继处拒绝或过滤——不只是界面上藏起来。@ 引用候选只列已共享会话;发消息、改写排队消息、斜杠命令里内嵌的会话引用指向未共享会话时,整个调用被拒绝。
125
+ ![配置向导第 3 步:自动连通性检测、手机扫码配对与桌面客户端配对](docs/images/wizard-step3-verify-pair.png)
182
126
 
183
- **关闭远程后在子客户端的表现**:服务端关闭某个会话的远程(或它闲置休眠)时,该会话直接从子客户端的侧边栏列表里隐藏——按「已归档」处理,在 DSH 里打开「显示已归档」筛选还能看到它。唯一的例外是此刻正打开着的会话页:页面停在原处,composer 上方出现「远程已关闭」横幅并注明原因(服务端关闭 / 闲置休眠 / 已在本机关闭),输入禁用;切到别的会话后它也随之隐藏。服务端重新开启远程(或重新共享)后,会话回到原工作区,打开着的页面自动恢复——横幅消失、输入恢复可用。
127
+ 1. **启动入口服务 & 自动连通自检**:
128
+ - 若选了「内置 Caddy」或「Cloudflare Tunnel」,先点顶部状态栏的**「启动 Caddy」**或**「创建并启动隧道」**。
129
+ - 向导会自动探测「本地网关就绪」与「访问地址连通」(自动验证外部访问 `/` 返回 `401` 配对页且 `/lan-gate/admin` 返回 `403` 隔离)。
130
+ 2. **配对手机 / 浏览器(Web 应用端)**:
131
+ - 保持在「配对手机 / 浏览器(扫码)」标签页,点击**「生成配对二维码」**。
132
+ - 用手机相机或浏览器扫码打开配对页,输入屏幕上的 8 位配对码即可进入 DSH;随后在手机浏览器菜单点**「添加到主屏幕」**安装为独立 PWA,并允许通知权限以接收锁屏推送。
133
+ 3. **配对另一台电脑(桌面客户端)**:
134
+ - 切换到**「配对另一台电脑(桌面客户端)」**标签页,点击**「生成桌面配对码」**,然后点击**「复制地址与配对码」**发给另一台电脑。
184
135
 
185
- **断线与恢复**:服务端(或链路)断线期间,已打开的远程会话页保持原状——横幅提示离线、输入禁用;连接恢复后自动续上:会话流与任务列表类面板自动重新拉取最新快照,不用手动刷新。例外是文件树变更与终端面板,断线后需要重新打开。
136
+ > **日常快捷操作**:配置完成后,无需再进向导——直接在插件详情页顶部的总览卡片底部点**「配对手机 / 扫码」**、**「配对另一台电脑」**或**「管理设备」**(支持重命名设备、切换设备角色、单独或全部移除设备、发送测试推送)。
186
137
 
187
138
  ---
188
139
 
189
- ## 配置
190
-
191
- ### 设置页配置引导
192
-
193
- **在哪打开**:DSH 左侧点「插件」→ 插件列表里点 dsh-zen-remote → 在「包含的组件」里再点一次 dsh-zen-remote,下面就是设置区块。
140
+ ### 场景 B:作为桌面客户端(连接远端服务端)
194
141
 
195
- **通用操作**:字段右上角出现「已覆盖」表示这个值保存在插件设置里(覆盖了配置文件或默认值),点「重置」恢复成下一层的值;改完点卡片底部的「保存」。需要重载的字段会有提示,保存后插件自己重载,不用重启 App。
142
+ 在另一台装了 DSH 桌面端的电脑上:
196
143
 
197
- **主服务端**
144
+ 1. 打开「插件」页 → `dsh-zen-remote` → 点击**「开始配置向导」**。
145
+ 2. **第 1 步**:选择**「作为桌面客户端(从机)」**,点击「下一步」。
146
+ 3. **第 2 步**:填写本机显示名称(如 `MacBook-Pro`)、主服务端的访问地址(公网必须为 `https://`,局域网 / Tailscale 可为 `http://192.168.x.x:3088` 或 `http://100.x.x.x:3088`)以及从主服务端复制来的 8 位桌面配对码,点击**「配对并连接」**。
147
+ 4. **第 3 步**:三盏状态灯(「本地配对凭证」「服务端连接」「会话与插件同步」)全绿即表示连接成功,左侧边栏会立即出现主服务端共享的工作区与会话分组。
198
148
 
199
- 1. 「运行角色」选「主服务端」,「设备名称」填一个好认的名字(比如「书房」)——子客户端侧边栏的分组标题、手机上都显示它。保存。
149
+ ---
200
150
 
201
- ![主服务端设置页:角色卡片里的运行角色与设备名称](docs/images/host-settings-role.png)
151
+ ## 3. 日常使用:会话共享与跨端协作
202
152
 
203
- 2. 「网关与反代」:只在本机和手机经反代访问时保持默认即可,公网访问见[配置公网访问](#配置公网访问主服务端)。要让局域网里的子客户端**直连**网关,把「监听地址」改成 `0.0.0.0`(必须包含本机回环,设置页靠回环地址管理网关)。
204
- 3. 「配对」:选「Web 应用端」(手机、浏览器)或「桌面应用端」(另一台 DSH 桌面端),点「生成配对码」,10 分钟内在对方那里输入。「设备」列表里可以改名、改设备角色、吊销。
153
+ | | **Web 应用端(手机 / 平板 / 浏览器)** | **桌面应用端(另一台 DSH 桌面客户端)** |
154
+ | --- | --- | --- |
155
+ | 可见范围 | 主服务端上的**全部**会话 | **仅**主服务端已开启「远程访问」的会话(服务端中继强制隔离) |
156
+ | 配对凭证 | 浏览器长期 HttpOnly Cookie | 插件配置中的加密设备令牌(Bearer Token) |
205
157
 
206
- ![生成配对码与设备列表](docs/images/host-pairing.png)
158
+ ### 如何开启或关闭单个会话的远程共享
207
159
 
208
- 4. 「远程共享」:闲置休眠时长、是否自动共享新会话。卡片下方的「已开启远程的会话」列出当前开着远程的会话、剩余闲置时间、是否有设备正在查看,可以逐个或全部关闭。
160
+ 服务端默认只将你选定的会话暴露给桌面客户端(也可在向导第 2 步勾选「新建会话自动开启远程共享」)。有四个开关入口:
209
161
 
210
- ![已开启远程的会话列表](docs/images/host-settings-shares.png)
162
+ 1. **主服务端侧边栏**:在会话行的「…」菜单里点**「开启远程 / 关闭远程」**;
211
163
 
212
- 5. 「推送」:推送内容摘要、回合结束推送等,按需开启,说明见[通知什么时候会响](#通知什么时候会响)。
164
+ ![主服务端在会话的「…」菜单里开启远程](docs/images/host-share-menu.png)
213
165
 
214
- **子客户端**
166
+ 2. **主服务端会话页标题栏**:点击标题右侧的地球图标(悬停可查看剩余闲置休眠时间;图标带绿点代表此刻有桌面客户端正在查看);
167
+ 3. **手机端(Web 应用端)会话页**:打开右上角会话信息卡片,切换**「远程访问」**开关;
215
168
 
216
- 1. 「运行角色」选「子客户端」,保存——插件自动重载,设置页换成子客户端的内容。
217
- 2. 「设备名称」填这台电脑的名字(比如「客厅」),配对时它会登记到服务端的设备列表里。保存。
218
- 3. 「连接服务端」:填服务端的网关地址(局域网例如 `http://192.168.1.10:3088`,公网必须 `https://`)和服务端生成的「桌面应用端」配对码,点「配对」。
219
- 4. 「连接状态」显示「已连接到『服务端设备名称』」就完成了,侧边栏随即出现服务端的分组。「诊断」区列出请求拦截器状态和最近的远程调用失败,排障先看这里。
169
+ <p align="center"><img src="docs/images/mobile-info-remote.png" alt="手机会话信息卡片里的「远程访问」开关" width="320"></p>
220
170
 
221
- ![子客户端设置页:角色、设备名称、连接服务端与连接状态](docs/images/client-settings.png)
171
+ 4. **主服务端设置页**:「已开启远程的会话」列表支持逐个关闭或一键全部关闭。
222
172
 
223
- > 截图中的地址、配对码为演示值。
173
+ ### 在子客户端上使用远程会话
224
174
 
225
- ### 字段参考
175
+ - **侧边栏分组**:服务端至少有一个会话开启远程后,子客户端侧边栏底部会出现**「服务端名 · 工作区名」**分组,点开即可像本地会话一样查看历史、流式对话、审批工具、使用终端、查看文件树与 Git diff、下载交付物或拖拽上传附件。
226
176
 
227
- 全部在 DSH「插件」页 → dsh-zen-remote 设置区块里改。每个字段按此顺序取第一个合法值:**环境变量 > 插件行设置 > `~/.dsh/lan-gate.config.json` > 默认值**;某层的值类型/范围/枚举不合法时该层视为未设置。改完需要重启网关/推送的字段(`role`、`port`、`host`、`targetPort`、`rateLimit`、`trustedProxies`、`vapidSubject`、`lang` 与推送各字段),插件会自己重载插件行——不用手动重启 App;其余字段立即生效。
177
+ ![子客户端侧边栏里的「书房 · Desktop」分组,标题行是远程连接状态图标](docs/images/client-sidebar.png)
228
178
 
229
- 插件行字段(与 `src/config.ts` 一致):
179
+ - **关闭与自动休眠**:开启远程的会话若连续 **48 小时**(`idleHours` 可调)无任何回合活动,会自动休眠关闭远程(正在运行或等待审批/回答的会话绝不休眠)。当会话关闭远程、或客户端主动取消配对/被服务端移除时,侧边栏会自动将其归档隐藏;正在查看的会话页会展示「远程已关闭」横幅并禁用输入,重新开启后自动恢复。
230
180
 
231
- | 字段 | 默认 | 说明 |
232
- | --- | --- | --- |
233
- | `role` | `host` | `host` 跑网关 + 推送 + 共享;`client` 连接服务端。不是精确的 `client` 就按 `host` 处理 |
234
- | `port` | `3088` | 网关端口;被占用自动往上试(最多 +20) |
235
- | `host` | `127.0.0.1` | 网关监听地址;反代不在本机时才需要放开 |
236
- | `targetPort` | (自动) | 本机 DSH Web UI 端口。留空即可:网关自动发现宿主实际监听端口(桌面端构建的端口可配置) |
237
- | `rateLimit` | `120` | 未配对请求的每分钟上限(按真实客户端 IP) |
238
- | `trustedProxies` | 空 | 逗号分隔 IP;反代不在本机时必填 |
239
- | `vapidSubject` | `mailto:admin@localhost` | 推送联系人。**iOS 必须改成真实邮箱或 https 网址**,否则 Apple 拒发 |
240
- | `lang` | `auto` | 网关页面、推送开关卡片与通知文案的语言。`auto` 跟随浏览器的 `Accept-Language`(认不出就用中文),`zh`/`en` 写死 |
241
- | `pushTurnEnd` | 关 | 「回合结束」也推一条。默认不推——回合结束不代表需要你。等授权/等回答的通知不受影响,永远推 |
242
- | `pushEvents` | `agent/turn-stopping` | 「回合结束」算哪些事件,逗号分隔;只在 `pushTurnEnd` 开时有意义 |
243
- | `pushDebounceMs` | `15000` | 两条自动推送的最小间隔;等授权/等回答的通知不受压制 |
244
- | `pushSummary` | 关 | 通知带上本回合的最终回复(只取正文,不含思考过程;截 120 字)和提问原文 |
245
- | `pushTool` | 开 | 设 `false` 关掉模型可调用的 `push_notify` 工具 |
246
- | `serverName` | 电脑名 | **设备名称**(≤ 40 字),两种角色共用:主服务端是子客户端分组标题里的显示名;子客户端是配对时注册的名字;与服务端双向自动同步 |
247
- | `idleHours` | `48` | 远程会话的闲置休眠时长(小时),范围 (0, 8760] |
248
- | `autoShareNewSessions` | 关 | 服务端新建的会话自动开启远程 |
249
- | `pluginBridgeConsent` | `{}` | 插件桥的整机数据同意表:插件名 → 布尔。设置页「插件桥」小节管理,一般不用手改;表里未登记插件的残留键不清理 |
250
-
251
- | `serverUrl`(子客户端) | 空 | 服务端网关地址,按上文规则校验 |
252
- | `deviceToken`(子客户端) | 空 | 配对拿到的设备令牌;密钥字段,处处打码 |
253
- | `turnFoldDesktop` | 关 | 回合过程折叠在任意宽度生效(默认仅手机宽度) |
254
- | `keyboardLiftRatio` / `keyboardLiftMaxPx` / `keyboardSafetyPadPx` | `0.42` / `400` / `15` | 软键盘抬升校准,给浏览器拿不到键盘高度的环境用(见已知问题一节) |
255
- | `maxUploadBytes` | 20 MB | 附件上传上限 |
256
-
257
- 旧的环境变量继续可用且优先级最高:`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` **不可**外部指定:插件每次加载现生成、自己交给网关。
258
-
259
- 上传上限、键盘校准、回合折叠这几个旋钮与 1.x 行为一致——只是从「插件行 YAML」搬到了「设置页」(手写插件行 YAML 也照样有效)。
181
+ ![远程被关闭后,会话页输入框上方的「远程已关闭」横幅](docs/images/client-remote-closed.png)
260
182
 
261
183
  ---
262
184
 
263
- ## 通知什么时候会响
264
-
265
- 默认只在**真正需要你**的时候响,分两条互不依赖的线。
266
-
267
- **一、系统自己判断的(恒开,且不受最小间隔压制)**
268
-
269
- | 情况 | 通知 |
270
- | --- | --- |
271
- | 某个工具在等你授权 | 「DSH 等你授权」,带工具名 |
272
- | 模型调用 `ask_user_question` 在等你回答 | 「DSH 等你回答」 |
273
-
274
- 这两类不看会话层级——子代理自己卡在授权上,照样喊你,因为等的还是你。也**不受最小间隔压制**:「有操作等你点头」是最不能被吞掉的一条。2.0 起桌面端 App 里跑的会话同样覆盖:钩子挂在服务端侧,会话在哪跑都一样。
275
-
276
- **有机器答复者时的时机**:审批事件的顺序是「先记 asked → 问答复者 → 记 decided」,所以推送并不是一见到 asked 就发,而是等 `DSH_PUSH_APPROVAL_GRACE_MS`(默认 5 秒)——这段时间内被答复掉的就不推。换了更慢的模型答复者就把这个值调大(实测平均 2.4 秒)。策略自动放行的授权不会打扰你。
185
+ ## 4. 锁屏推送:通知什么时候会响
277
186
 
278
- **二、模型自己决定的**
187
+ 默认只在**真正需要你介入**的时候响,分为两条独立通道(推送载荷全程 `aes128gcm` 端到端加密):
279
188
 
280
- `push_notify` 工具,模型在这些时候该调:你明确要求做完通知、需要你介入才能继续、出现你大概率想立刻知道的意外。不该调的场景同样写在工具描述里,同一段文字还作为会话上下文注入,两处共用同一个常量、不会各改各的。
281
-
282
- **默认不会响的**:普通跑完一轮不推(想要旧行为设 `pushTurnEnd` 或 `DSH_PUSH_TURN_END=1`);子代理跑完永远不推,无论上面那个开关。
283
-
284
- **通知里写什么**:默认只有标题。开 `pushSummary` 才带这一轮的最终回复——只取正文,不含思考过程;这一轮没说话就退回「最后执行了 xx 工具」。推送 payload 是 aes128gcm 端到端加密的。
189
+ 1. **系统自动触发(恒开,不受最小间隔压制)**:
190
+ - **工具等待授权**:推送「DSH 等你授权」(附带工具名)。为配合自动审批插件,默认等待 `DSH_PUSH_APPROVAL_GRACE_MS`(5 秒),5 秒内已被自动审批处理的操作不会打扰你。
191
+ - **模型调用 `ask_user_question` 等待回答**:推送「DSH 等你回答」。
192
+ - 子代理会话卡在授权或提问上时同样会推送。
193
+ 2. **模型主动调用(`push_notify` 工具)**:
194
+ - 当你交代「做完告诉我」、或模型遇到阻塞必须你拍板时,模型会主动调用 `push_notify` 推送到手机锁屏。
195
+ - 普通回合结束默认不推送(如需每轮结束都推,可在进阶配置中打开 `pushTurnEnd`);开启 `pushSummary` 可在通知正文中附带本轮最终回复摘要(120 字以内,不含思考过程)。
285
196
 
286
197
  ---
287
198
 
288
- ## 功能
289
-
290
- **主服务端(手机访问,1.x 的延续)**
291
-
292
- - 会话列表主屏 + 独立会话页两级页面栈,横向推入推出
293
- - 主屏插件入口 chips,按已装插件自动出现,显隐可自定义
294
- - composer 重排:控件图标化,权限/模型菜单变成底部 sheet
295
- - 会话信息卡:六格统计 + 导出日志 / 重命名 / Fork / 归档;分享图一键导出 PNG 长图;2.0 起多一行「远程访问」开关
296
- - 回合过程折叠、左缘右滑返回、安卓返回手势接管、手机本地附件上传
297
- - 配对码换长期设备令牌,认令牌不认 IP,可随时吊销
298
- - 真 PWA + 真 Web Push(VAPID + aes128gcm),默认只在等授权/等回答时响
299
- - 冷启动从第一帧就画出手机主屏骨架;带内容哈希的构建产物长缓存,重复打开不再重下应用壳
300
-
301
- **主服务端(2.0 新增)**
302
-
303
- - 会话共享:四个开关入口(含手机信息卡片)、闲置休眠、新建自动共享、子会话跟随
304
- - 以上全部在 DSH 跑**桌面端 App** 时同样成立:网关自动转发到桌面端后台的实际端口,桌面端会话的等授权/等回答照推手机
305
-
306
- **桌面应用端(2.0 新增)**
307
-
308
- - 侧边栏远程分组(「服务端名 · 工作区名」),远程会话与本地会话同等使用:完整历史、实时进度、发消息、取消、排队调整、审批与提问应答、模型选择、文件树、改动列表与改动摘要 / diff、目标、斜杠命令、预设切换、子智能体提问/中断、附件与 @ 引用、开在服务端的终端
309
- - 远程会话里的产物文件可**下载到本机**:产出台卡片与文档预览头各一个「下载到本机」,经中继按窗口读取服务端文件字节后由浏览器落盘(任何大小的文件都走分段读取,不占满内存);Word/Excel/PPT 预览改由服务端 LibreOffice 出 PDF(`officeToPdf/*` 已登记进中继白名单),不再报「此功能暂不支持远程会话」
310
- - 远程会话可附加**非图片文件**:上传请求由子客户端在本机后台接住、经中继流式转到服务端会话;上限 500 MiB 是中继自定的(本机上传无此限),超限在子客户端本地即提示;慢速链路受客户端侧 15 分钟上传预算约束(宿主 HTTP 服务的 5 分钟请求期限才是实际上限:500 MiB 约需 14 Mbps 持续带宽)
311
- - 远程会话里的模型下拉框显示**服务端**的模型目录(分组名「服务端名 · 分组名」),本机会话只显示本机模型;中继离线时保留上次拉到的服务端分组;视觉路由等本机设置里不会出现服务端分组。已知边界:服务端新建且未发过消息的会话,下拉框按本机默认模型显示
312
- - 远程分组里新建的会话跑在服务端对应工作区、自动开启远程;远程会话可改名、归档、置顶、分叉,也可从子客户端关闭其远程(带确认)
313
- - 远程会话标题行连接图标(在线 / 离线 / 版本有差异);会话被休眠或服务端关闭时显示「远程已关闭」及原因;服务端离线时分组变灰、输入禁用,后台自动退避重连
314
- - 审批/提问先到先得:服务端自己的界面先答了,子客户端自动同步成「已处理」
315
-
316
- **两端(2.1 新增)**
317
-
318
- - **插件桥**:其他插件登记一份桥接声明后,它的面板数据在远程会话里取自主服务端——子客户端本机把声明命中的 GET 请求改道到服务端,服务端以本机回环执行、流式交回,本机数据绝不混入远程会话。两端插件名、确切版本与声明一致才算已桥接;整机数据另需主服务端主人在设置页逐插件同意。接入指南见[插件桥](docs/plugin-bridge.md)
319
-
320
- **桌面应用端(2.2 新增)**
321
-
322
- - 往远程会话里拖文件一律走**上传**:桌面端原本有「同机零拷贝」捷径——拖入的文件带着本机磁盘路径,会被变成 `@本机绝对路径` 的引用(服务端读不到你这台机器上的文件)。浏览器半边把这个拖拽拦截下来,改写成普通上传——与附件按钮同一条中继链路,服务端会话拿到的是文件字节。往远程会话拖**文件夹**暂不支持:拖拽会被拦下并短暂提示。本机会话不受影响,捷径照常;守卫的状态可在设置页诊断区查看。
323
-
324
- 深度说明:[界面](docs/interface.md) · [公网接入 + 中继协议](docs/remote-access.md)
199
+ ## 5. 功能一览
200
+
201
+ - **移动端 PWA 界面(主服务端)**:
202
+ - 会话列表主屏 + 独立会话页两级页面栈(支持左缘右滑返回、安卓返回手势接管、冷启动首帧骨架屏与内容哈希长缓存)
203
+ - 主屏第三方插件入口 chips(按已装插件自动浮现,可自定义显隐)
204
+ - 底部重排 composer(图标化按钮,权限与模型切换改为底部 Sheet,支持手机本地拍照/文件上传)
205
+ - 会话信息卡(六格 token/耗时统计、导出日志、重命名、Fork、归档、一键导出 PNG 分享长图、远程访问开关)
206
+ - **一体化配置与配对向导(2.3 新增)**:
207
+ - 插件详情页顶部集成 2×2 实时状态总览与三步向导弹窗,统一管理服务端四路接入(内置 Caddy、已有反代、Cloudflare Tunnel、局域网/Tailscale 直连)与桌面客户端配对
208
+ - 预编译 `Caddy + caddy-dns/cloudflare` 与 `cloudflared` 自动下载,内置中国大陆网络自动探测与镜像加速(`gh-proxy.com` / `ghfast.top`)、连接超时与断流自动切换源,并由 GitHub Actions 每日巡检跟随上游版本自动更新
209
+ - 局域网 / Tailscale 模式自动探测本机内网 IP 自检,网关内置私网源 IP 铁闸拦截公网未代理明文直连
210
+ - 独立「已配对设备管理」弹窗,以及客户端解配/吊销时自动归档清理侧边栏残留会话
211
+ - **跨桌面端远程会话(桌面客户端)**:
212
+ - 侧边栏远程工作区分组,支持完整对话流、审批/提问抢答同步、服务端模型目录同步、文件树与改动 diff、服务端远程终端
213
+ - 交付物分段流式下载到本机、服务端 LibreOffice 渲染 Office 文档 PDF 预览、最大 500 MiB 非图片大文件流式上传与拖拽文件自动转上传
214
+ - **第三方插件桥(Plugin Bridge)**:
215
+ - 协作插件登记桥接声明或通过内置适配器(如 `dsh-better-sidebar`),即可在子客户端的远程会话中直接读写主服务端的数据面板(整机数据需服务端主人在设置页逐插件授权)。详见 [插件桥文档](docs/plugin-bridge.md)。
216
+
217
+ 深度说明:[界面设计文档](docs/interface.md) · [公网接入与中继协议](docs/remote-access.md)
325
218
 
326
219
  ---
327
220
 
328
- ## 版本容错
329
-
330
- 三层,从严到宽:
331
-
332
- 1. **中继协议版本**必须一致——实际操作上,**两端 zen-remote 需为相同版本**。服务端若还是 1.x,它的网关不认子客户端的 Bearer 令牌,请求会撞上通用配对墙,子客户端报「令牌已失效(或服务端 zen-remote 低于 2.0.0)」。DSH 版本可以不同。
333
- 2. **DSH 接口指纹**决定其余:两端各自对中继会用到的几组远程接口生成规范化指纹,握手时比对。全部一致即完全兼容,版本号不同也无关;有差异照常连接,远程分组标注「(版本有差异)」,设置页诊断区列出差异组,只有受影响的面板降级。
334
- 3. **运行时降级**:某次转发调用因参数或结果校验失败,只有那个面板显示「与服务端版本不兼容」并记入诊断区,其余功能继续可用。
221
+ ## 6. 进阶配置与字段参考
335
222
 
336
- ---
337
-
338
- ## 安全模型
223
+ 日常使用只需通过插件详情页顶部的**「远程访问与设备配对」向导**即可完成全部配置。若需微调底层参数,可在 `dsh-zen-remote` 插件详情页的**「包含的组件」**下再次点击 `dsh-zen-remote` 展开底层配置卡片。
339
224
 
340
- 一台已配对设备被信任到什么程度,明说:
225
+ <details>
226
+ <summary>查看底层配置卡片截图与全部字段参考表</summary>
341
227
 
342
- - **已配对的桌面应用端被视为可信设备。** 远程会话里的终端是以**服务端用户**身份运行的 shell,不受智能体沙箱与审批限制。文件预览接口(`workspaceFiles/read` 等)**不**限制在会话目录内——服务端进程读得到的文件都读得到。已配对设备可以在服务端**任意**工作区新建会话(不受「开启远程」限制;新建的会话自动开启远程)。
343
- - **「只有开启远程的会话可见」约束的是会话数据**:未开启远程的会话,其列表、历史、实时进度、审批/提问事件都被中继拒绝或过滤;@ 引用候选**只列已共享会话**;发消息、改写排队消息、斜杠命令里内嵌的 `dsh-session:` 会话引用一旦指向未开启远程的会话,整个调用被服务端拒绝。它不是围住整台机器的沙箱。
344
- - **管理操作只在服务端本机**:生成配对码、改角色、吊销只能在跑服务端那台机器的设置页做——经网关进来的请求可以查看状态,但变更动作被拒——唯一例外是开关会话的远程:Web 应用端(手机)可以开关,手机信息卡片里的「远程访问」开关就走这条;桌面应用端根本够不到管理路由。
228
+ 每个字段按此顺序取第一个合法值:**环境变量 > 插件行设置 > `~/.dsh/lan-gate.config.json` > 默认值**。修改需要重启网关/推送的字段后插件会自动重载,无需重启 App。
345
229
 
346
- ---
230
+ | 主服务端角色与配对卡片 | 已开启远程的会话列表 | 子客户端配置与诊断卡片 |
231
+ | --- | --- | --- |
232
+ | ![主服务端角色](docs/images/host-settings-role.png) ![配对与设备](docs/images/host-pairing.png) | ![已开启远程的会话](docs/images/host-settings-shares.png) | ![子客户端设置](docs/images/client-settings.png) |
347
233
 
348
- ## 已知限制
234
+ | 字段 | 默认值 | 说明 |
235
+ | --- | --- | --- |
236
+ | `role` | `host` | `host` 运行网关 + 推送 + 共享;`client` 作为桌面客户端连接远端服务端 |
237
+ | `serverName` | 电脑名 | **设备名称**(≤ 40 字),主服务端作侧边栏分组标题,子客户端作配对登记名,双向自动同步 |
238
+ | `port` | `3088` | 本地网关端口;被占用时自动向上递增重试(最多 +20) |
239
+ | `host` | `127.0.0.1` | 网关监听地址;本机反代/隧道保持 `127.0.0.1`,局域网/Tailscale 直连设为 `0.0.0.0` |
240
+ | `targetPort` | (自动) | 本机 DSH Web UI 端口,留空自动探测宿主实际监听端口 |
241
+ | `rateLimit` | `120` | 未配对请求每分钟速率上限(按真实客户端 IP 计数) |
242
+ | `trustedProxies` | 空 | 逗号分隔的受信任反代 IP(反代不在本机时配置) |
243
+ | `vapidSubject` | `mailto:admin@localhost` | Web Push 联系标识(**iOS 必须为真实邮箱或 `https://` 网址**,否则 Apple 拒收) |
244
+ | `lang` | `auto` | 网关页面与通知语言:`auto`(跟随浏览器)、`zh`、`en` |
245
+ | `pushTurnEnd` | 关 | 普通回合结束时是否也发送推送(等授权/等回答的通知恒开,不受此项影响) |
246
+ | `pushEvents` | `agent/turn-stopping` | 触发回合结束推送的事件名(逗号分隔) |
247
+ | `pushDebounceMs` | `15000` | 两条自动推送之间的最小间隔毫秒数 |
248
+ | `pushSummary` | 关 | 推送通知正文是否附带本回合最终回复摘要(截断至 120 字) |
249
+ | `pushTool` | 开 | 是否向模型注册 `push_notify` 主动推送工具 |
250
+ | `idleHours` | `48` | 远程共享会话的闲置自动休眠时长(小时,范围 `(0, 8760]`) |
251
+ | `autoShareNewSessions` | 关 | 主服务端新建的会话是否自动开启远程共享 |
252
+ | `pluginBridgeConsent` | `{}` | 插件桥整机数据授权表(插件名 → 布尔值) |
253
+ | `serverUrl`(客户端) | 空 | 远端主服务端的网关地址(公网强制 `https://`,内网允许 `http://`) |
254
+ | `deviceToken`(客户端) | 空 | 配对签发的客户端设备令牌(密钥字段,界面脱敏) |
255
+ | `turnFoldDesktop` | 关 | 是否在桌面端宽度也默认折叠回合中间过程 |
256
+ | `keyboardLiftRatio` / `keyboardLiftMaxPx` / `keyboardSafetyPadPx` | `0.42` / `400` / `15` | 软键盘高度不可见环境下的输入框抬升校准参数 |
257
+ | `maxUploadBytes` | 20 MB | 手机端单次附件上传大小上限 |
258
+
259
+ 历史环境变量(优先级高于插件行):`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`、`DSH_PUSH_APPROVAL_GRACE_MS`。
349
260
 
350
- - 同时开多个 DSH 窗口(包括用浏览器打开桌面端的后台页面)时,子客户端对「当前打开的是哪个会话」可能判断不准:关闭远程的会话隐藏时机可能不对,极端情况下正打开着的会话会被当成后台会话、被踢回首页。
351
- - 换到另一台服务端后再换回原来那台,或服务端删除某工作区后又以同一 id 重建:相关远程分组要**重载页面**才会再出现(DSH 侧边栏对移除过的工作区 id,在页面生命周期内不再接受)。
352
- - 远程会话里「导出会话」已禁用(菜单项隐藏,后台对虚拟会话 id 一律拒绝);改动面板的**摘要与 diff 可以看**(经中继转发),但「打开 / 在访达中显示 / 在应用中打开」这类会在服务端机器上弹窗的入口在远程会话里隐藏(交付物卡片同理)。
353
- - @ 引用候选由宿主**先截前 50 条再按共享过滤**:服务端会话很多时,已共享会话可能不出现在候选列表里(不影响引用检查本身——直接写出的引用仍会被逐条校验)。
354
- - 第三方插件自己的接口要经[插件桥](docs/plugin-bridge.md)才能在远程会话里工作:协作插件自行登记,第三方插件靠内置适配器。都没接的插件,其面板在远程会话里降级或隐藏(插件的**工具**跑在服务端,不受影响)。
355
- - 装了 [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) 时,远程会话里它有两档处理:**0.25.0、0.26.0、0.26.1、0.28.0-alpha.0 与 0.28.0** 五版由内置适配器桥接——面板读写的是主服务端的数据(整机数据,需在主服务端设置页逐插件同意,见[安全边界](docs/remote-access.md));**其他版本维持隐藏**——那些版本的面板读写的是子客户端**本机**的文件,且因为它接管了宿主的「文件」标签,未桥接的远程会话里侧栏文件 tab 显示「未桥接」说明占位;聊天或交付物里的文件链接在远程会话里会打开一个空的右栏。
356
- - 两端 zen-remote 需为**相同版本**(中继协议版本一致);DSH 版本可以不同,接口指纹有差异时远程分组标注「(版本有差异)」。
261
+ </details>
357
262
 
358
263
  ---
359
264
 
360
- ## 已知问题
361
-
362
- **iOS 26.x 独立 PWA 视口缩水**:加到主屏后视口底部会少掉一条状态栏高度,普通 Safari 标签页正常。这是 iOS 系统缺陷,缺掉的区域在文档之外,CSS 够不着;本插件做了三层缓解(浅色 manifest 背景 + 安全区补偿 + 强制重排),能减轻但不保证复原。彻底恢复只能整个 App 退出重开。
265
+ ## 7. 架构、安全模型与已知说明
363
266
 
364
- **iOS 26+ 独立 PWA 窗口顶部系统渐进模糊**:加到主屏后从桌面打开时,iOS WebKit 会在窗口顶部约 95px 区域(状态栏至顶栏上半截)叠加一层系统级滚动边缘模糊遮罩(`WKScrollPocket` / `UIScrollEdgeEffect`)。该遮罩由系统合成层绘制在网页 DOM 之外,CSS 无法直接消除;点进任意会话再退回会话列表页即可在列表页恢复正常。Android 与普通 Safari 标签页不受影响。
267
+ ### 安全模型与信任边界
365
268
 
366
- **个别环境软键盘对浏览器完全不可见,输入框抬升靠估算兜底**:部分组合里系统不把键盘高度告知页面(visualViewport、VirtualKeyboard API 一并失效,均已实测排除)。插件聚焦后探测约 1.2 秒,判定「键盘不可见」就按估计高度抬升输入框。抬升高度差得明显的话,在设置页里调 `keyboardLiftRatio` / `keyboardLiftMaxPx` / `keyboardSafetyPadPx`。正常环境完全不走这条路径。
269
+ - **已配对的桌面客户端属于受信任设备**:远程会话中的终端以**服务端当前系统用户**权限运行,文件预览接口可读取服务端进程有权限访问的文件,且可在服务端任意工作区新建会话。
270
+ - **未开启远程的会话严格隔离**:未共享会话的列表、历史、流式事件、审批/提问以及内嵌的 `dsh-session:` 引用均在服务端中继层直接过滤或拒绝,绝不仅是在前端隐藏。
271
+ - **公网明文直连防护**:即便在局域网模式下将网关监听设为 `0.0.0.0`,网关底层的 `isPrivateOrLoopbackIp` 铁闸也只放行本机回环、RFC1918 局域网段与 Tailscale(`100.64.0.0/10` / `fd7a:115c:a1e0::/48`)的直连请求;任何来自公网 IP 的未代理直连一律返回 `403 public-direct-forbidden`。
272
+ - **管理操作仅限服务端本机**:生成配对码、修改设备角色、配置向导、吊销设备均只允许在服务端本机操作。
273
+ - **数据与隐私**:插件自身不向任何第三方收集或上报数据。配对状态存于 `~/.dsh/lan-gate-state.json`,共享表存于 `~/.dsh/zen-remote-shares.json`,托管的 Cloudflare 令牌以 `0600` 权限存于 `~/.dsh/zen-remote-ingress/`。
367
274
 
368
- **经反代访问时设置页打不开**(插件配置列表空白、模型卡片报「settings are unavailable in this browser」):DSH 官方设计是设置类 RPC 只对回环连接开放,远程浏览器的设置镜像初始即 `unavailable`。绕法:回跑 DSH 的那台机器用本机浏览器改。经网关打开的插件设置页同样受此限制。与本插件无关。
369
-
370
- ---
275
+ ### 版本容错
371
276
 
372
- ## 权限与数据
277
+ 1. **中继协议版本**:主服务端与子客户端的 `dsh-zen-remote` 版本需保持一致。
278
+ 2. **DSH 宿主版本**:两端 DSH 版本可以不同。连接握手时自动比对接口组规范化指纹,存在差异的分组在侧边栏标注「(版本有差异)」,仅受影响的单面板降级,其余功能照常工作。
373
279
 
374
- - **网络**:网关只监听本机(默认 `127.0.0.1:3088`),对外暴露完全由你的反代/隧道决定;推送经浏览器推送服务商中转(aes128gcm 端到端加密);桌面应用端与主服务端之间的中继也走这条网关,授权在服务端逐请求复核。插件自身不向任何第三方上报数据。
375
- - **文件**:附件上传只写入当前会话工作目录下的 `.dsh-uploads/`;配对状态在 `~/.dsh/lan-gate-state.json`,配置在 `~/.dsh/lan-gate.config.json`,共享表在 `~/.dsh/zen-remote-shares.json`,服务端 id 在 `~/.dsh/zen-remote-server.json`。
376
- - **凭据**:不收集、不存储任何账号密码;设备身份是本插件自己签发的随机令牌(Web 应用端为 HttpOnly Cookie,桌面应用端为密钥字段),外加一枚每次加载现生成的中继共享密钥,不出这台机器对。
280
+ ### 已知限制与常见问题
377
281
 
378
- 排障:运行日志在 `~/.dsh/logs/web.log`(网关与推送的行带 `[dsh-zen-remote-*]` 前缀;桌面端构建打到 App 控制台)。安全问题请走 GitHub Security Advisories 私下报告,不要公开提 issue。
282
+ <details>
283
+ <summary>展开查看已知限制、iOS PWA 适配与已适配第三方插件列表</summary>
379
284
 
380
- ---
285
+ **已知限制**
286
+ - 同时打开多个 DSH 窗口时,子客户端对「当前正打开哪个会话」的判断可能存在偏差。
287
+ - 切换服务端或服务端重建同 ID 工作区后,需刷新一次客户端页面以重新加载侧边栏分组。
288
+ - 远程会话禁用「导出会话」,并隐藏会在服务端机器本机弹窗的「在访达中显示 / 在应用中打开」按钮。
289
+ - 经反向代理在外部浏览器访问时,DSH 宿主自身的「设置」页不可修改(DSH 官方设计将设置 RPC 限制为本机回环连接),修改插件配置请直接在服务端本机操作。
381
290
 
382
- ## 已适配的第三方插件
291
+ **iOS 独立 PWA 已知系统问题**
292
+ - **iOS 26.x 独立 PWA 视口缩水**:添加到主屏幕后底部可能少一条状态栏高度(iOS WebKit 缺陷),插件已内置三层缓解,彻底恢复需划掉 App 重开。
293
+ - **iOS 26+ 顶部系统渐进模糊**:从主屏幕打开时顶部约 95px 区域可能出现系统合成层模糊遮罩(`WKScrollPocket`),点进任意会话再返回列表页即可消除。
383
294
 
384
- 移动端 UI 对下列插件做了专门适配。所有适配都锚定对应插件自己的 DOM 标记;插件桥另算一类——插件自己登记声明即可(见[插件桥](docs/plugin-bridge.md)),不依赖本表:
385
- 没装该插件时规则不生效,装了未列出的插件也不会被误伤。
295
+ **已适配的第三方插件**
386
296
 
387
- | 插件 | 移动端适配内容 | 实测版本 |
297
+ | 插件 | 移动端与远程适配内容 | 实测版本 |
388
298
  | --- | --- | --- |
389
- | [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) | 0.19+:它的全部 tab 都在 DSH 0.1.5 官方右侧栏里、手机上宿主自己切全屏(避让刘海与底部指示条)——会话页头部留入口按钮,左缘回退手势收它,文件树里点 @ 引用后自动收起。≤ 0.18(自绘右栏,只在 DSH < 0.1.5 上):手机全宽抽屉并避让刘海安全区、底部居中的关闭按钮。**插件桥**:**0.25.0、0.26.0、0.26.1、0.28.0-alpha.0 与 0.28.0** 五版由 zen-remote 的内置适配器桥接——远程会话里它的文件 / git / 任务面板读写的是主服务端的数据(整机数据,需在主服务端设置页逐插件同意;等于让桌面应用端读写服务端整盘并执行 git 写操作,见[安全边界](docs/remote-access.md));其他版本不桥接,远程会话维持「隐藏本机界面」的原有处理 | 0.19.1(老版规则 0.15.0;适配器 0.25.0, 0.26.0, 0.26.1, 0.28.0-alpha.0, 0.28.0) |
390
- | [@nanmicoder/dsh-agent-teams](https://github.com/NanmiCoder/dsh-agent-teams) | AgentTeams 活动浮层挪到会话头部下方(原位置压住头部按钮)、会话列表页自动隐藏;子代理会话头部保留可点的父会话标题,一键切回主会话 | 0.1.9 |
391
- | [@ychris12138/dsh-usage-stats](https://github.com/Ychris12138/dsh-usage-stats) | 用量与余额入口收进主屏 chips 行 | 0.2.9 |
392
- | [@opendsh/dsh-plugin-scheduled-tasks](https://github.com/Ceelog/dsh-plugins) | 定时任务入口收进主屏 chips 行 | 0.2.3 |
393
- | dsh-at-file | @文件引用,配合附件上传的 `@` 路径引用使用;它和本插件的附件 chip 读同一份草稿 token,手机端隐藏它 `.dsh-uploads/` 下那几行以免同一个文件被画两遍(缩略图 + 文件名),其余 `@` 引用不动 | 0.6.7 |
394
- | [@ace-zone/dsh-market](https://www.npmjs.com/package/@ace-zone/dsh-market) | 插件市场弹窗顶栏在手机上放不下,关闭的 × 被挤出面板外(触屏没有 Esc,等于关不掉);隐藏标语 / 版本号 / 官网链接三个装饰位,标题改成省略号收缩,语言切换和 × 保留并加大点按面积 | 0.1.66 |
395
- | [dsh-vision-toolkit](https://www.npmjs.com/package/@anionex/dsh-vision-toolkit) | 图像 Q&A/OCR,配合手机端附件上传使用 | — |
396
- | [dsh-web-ui 全家桶](https://www.npmjs.com/package/@linxin666/dsh-web-ui-all) | 沿用上游 dsh-web-mobile 的兼容规则(文件树 / 预览浮层限宽居中等) | — |
397
-
398
- **混合版本**——桥接与否逐端判定:桥不桥看**服务端**装的 better-sidebar 版本(在适配器版本表里 + 主人同意);界面发什么请求看**子客户端**自己的版本(UI 在本机加载);两端 zen-remote 不同版则适配器摘要比对失败、退隐藏档:
399
-
400
- (旧 zen-remote + 服务端 BS 0.25 + 客户端 BS 0.25 即**当前已发布版**的现状,照常桥接。)
401
-
402
- | 两端 zen-remote | 服务端 BS | 客户端 BS | 结果 |
403
- | --- | --- | --- | --- |
404
- | 新(含 0.26 适配) | 0.26 | 0.26 | 全量桥接,新功能全可用 |
405
- | 新 | 0.25 | 0.25 | 照常桥接,行为与旧版一致 |
406
- | 新 | 0.25 | 0.26 | 桥接;0.26 新方法与 `/sidebar/write` 被 0.25 服务端 404、`ws/sidechat` 被拒后走 15s 轮询——单功能降级,核心侧聊通,不误路由不写本机 |
407
- | 新 | 0.26 | 0.25 | 桥接;UI 不发新请求,等同 0.25 体验 |
408
- | 旧(≤ 2.2.1) | 0.26 | 任意 | 不在适配器版本表 → 未桥接隐藏档 |
409
- | 旧 | 0.25 | 0.26 | 按 0.25 桥接;新方法被适配器以 `adapter-method-unknown` 拒绝,`/sidebar/write` 带虚拟 id 落本机插件报错(不写文件)——单功能降级 |
410
- | 一新一旧 | 任意 | 任意 | 核心远程会话可用,适配器摘要不同 → 桥接比对失败 → 隐藏档,直到两端同版 |
411
-
412
- 各项适配的技术细节(锚点选择器、断点、取舍记录)见[界面文档](docs/interface.md)的「兼容插件」一节。
299
+ | [dsh-better-sidebar](https://github.com/omdsh-dev/DSH-better-sidebar) | 移动端右栏全屏抽屉、左缘滑回与安全区避让;**插件桥**内置适配 `0.25.0` 至 `0.28.2` 七版(服务端授权后远程会话可直接读写服务端文件/Git/任务面板) | 0.19.1 / 适配器 0.25–0.28.2 |
300
+ | [@nanmicoder/dsh-agent-teams](https://github.com/NanmiCoder/dsh-agent-teams) | AgentTeams 活动浮层避让会话顶栏、子代理头部保留父会话一键返回 | 0.1.9 |
301
+ | [@ychris12138/dsh-usage-stats](https://github.com/Ychris12138/dsh-usage-stats) | 用量与余额入口收进手机主屏 chips 行 | 0.2.9 |
302
+ | [@opendsh/dsh-plugin-scheduled-tasks](https://github.com/Ceelog/dsh-plugins) | 定时任务入口收进手机主屏 chips 行 | 0.2.3 |
303
+ | dsh-at-file | 手机端去重 `.dsh-uploads/` 附件引用行 | 0.6.7 |
304
+ | [@ace-zone/dsh-market](https://www.npmjs.com/package/@ace-zone/dsh-market) | 插件市场弹窗顶栏移动端紧凑化,保留关闭按钮与语言切换 | 0.1.66 |
305
+ | [dsh-vision-toolkit](https://www.npmjs.com/package/@anionex/dsh-vision-toolkit) | 配合手机端附件上传进行图像问答/OCR | — |
306
+ | [dsh-web-ui 全家桶](https://www.npmjs.com/package/@linxin666/dsh-web-ui-all) | 沿用文件树与预览浮层移动端限宽居中规则 | — |
307
+
308
+ </details>
413
309
 
414
310
  ---
415
311