dsh-plugin-mobile-gateway 0.7.1 → 0.7.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,137 @@
1
+ # dsh-mobile 多网关支持待办
2
+
3
+ ## 目标与范围
4
+
5
+ 一个 dsh-mobile 客户端可以添加、保存并连接部署在云服务器或不同 PC 上的多个独立 Gateway。各网关拥有独立的凭证、连接状态和业务数据。
6
+
7
+ 第一阶段支持多网关管理与切换连接;第二阶段支持前台同时连接多个网关;第三阶段按需增加局域网自动发现。第一阶段无需中心服务器。
8
+
9
+ 2026-09-09:第一阶段 Gateway 插件 G1–G4 已实现并通过自动化回归,以下已完成项打勾。App 未修改,后续阶段及人工多机验收尚未完成。详见 [App 对接说明](multi-gateway-app-integration.md) 与 [验收报告及人工步骤](multi-gateway-phase1-acceptance.md)。
10
+
11
+ ## 实施前基础
12
+
13
+ - 启动配置 `gatewayEnabled: true` 已支持常驻,不会启动无人连接自动关闭计时器。
14
+ - 管理界面手动开启后,默认 5 分钟无人连接才自动关闭;成功连接后会清除计时器,后续断线不会重新计时。
15
+ - 已有一次性配对码、长期设备 token、设备撤销和独立局域网 WebSocket 监听。
16
+ - 已有 `control` / `conversation` 通道拆分,适合后续多网关并行连接。
17
+ - 当前 `hello` 和配对载荷没有稳定的网关身份字段,局域网监听也不等于自动发现。
18
+
19
+ ## 第一阶段:网关插件待办
20
+
21
+ ### G1. 明确并持久化常驻模式
22
+
23
+ - [x] 将运行模式明确为关闭、临时开启、常驻开启;管理界面显示当前模式。
24
+ - [x] 常驻开启不启动无人连接关闭计时器;临时开启保留当前等待首次连接的超时行为。
25
+ - [x] 持久化用户选择;优先级为已保存模式 > `gatewayMode` > 旧 `gatewayEnabled`。临时模式重启重新计时,自动关闭后保存关闭状态。
26
+ - [x] 管理界面切换到常驻后,重启仍保持常驻;主动关闭后按约定保持关闭。
27
+ - [x] 保留配对码有效期、请求超时和文件传输空闲超时,避免与网关运行模式混淆。
28
+ - [x] 更新管理接口、界面文案、配置说明与部署说明,注明常驻依赖 DSH 进程运行及机器网络可达。
29
+
30
+ ### G2. 增加稳定网关身份
31
+
32
+ - [x] 首次启动生成随机 `gatewayId` 并持久化,重启、升级、地址变化时保持不变。
33
+ - [x] 支持可配置的 `gatewayName`,用于展示机器名称;App 可单独保存本地别名。
34
+ - [x] 明确身份文件的保存位置(默认 `<deviceFile>.gateway.json`)、原子写入与损坏处理,避免损坏后静默生成新身份。
35
+ - [x] 说明克隆部署时必须生成新的网关身份,避免多台机器复用同一 `gatewayId`。
36
+ - [x] 在 `hello`、首次配对成功帧和本机管理状态中增加网关身份信息。
37
+ - [x] 配对载荷携带 `gatewayId`、`gatewayName`,协议字段命名在实现前与 App 对齐。
38
+
39
+ ### G3. 支持同一网关的多个访问地址
40
+
41
+ - [x] 定义可选的 `endpoints: string[]` 字段,在配对载荷及本机管理状态返回候选地址,并保留现有 `publicUrl` 兼容字段。
42
+ - [x] 允许管理员配置并在配对时提供多个候选地址,不把监听地址 `0.0.0.0` 当作可连接地址。
43
+ - [x] 多个地址指向同一网关时返回相同身份,控制和会话通道返回相同身份。
44
+ - [x] 候选地址仅作为连接提示,不作为身份认证依据;地址变化不自动建立信任。
45
+
46
+ ### G4. 协议与兼容性
47
+
48
+ - [x] 更新 `PROTOCOL.md`,给出网关身份、地址列表、常驻模式和配对示例。
49
+ - [x] 优先采用可选字段扩展,保持已有请求与响应含义不变;验证旧 App 的解码行为后再决定是否升级配对载荷版本。
50
+ - [x] 保持每个网关独立签发和撤销设备 token,禁止引入跨网关通用 token。
51
+ - [x] 增加验证:身份持久化、常驻无客户端、临时模式超时、模式切换与重启、旧客户端兼容、双通道身份一致。
52
+
53
+ ## 第一阶段:App 待办
54
+
55
+ ### A1. 多网关资料与迁移
56
+
57
+ - [ ] 建立 `GatewayProfile`:本地记录 ID、远端 `gatewayId`(旧网关允许缺失)、名称、本地别名、候选地址、凭证引用、最近连接时间。
58
+ - [ ] 将现有单网关配置迁移为默认网关,保留原凭证与用户数据;迁移可重复执行且不产生重复记录。
59
+ - [ ] 按网关隔离安全存储中的 token 和服务端返回的 device ID;客户端安装级设备 ID 可继续复用,不能代替鉴权凭证。
60
+ - [ ] 支持添加、重命名、排序、删除网关和选择默认网关。
61
+ - [ ] 再次扫描同一网关时更新已有记录;若重新配对导致 token 轮换,替换凭证并重建该网关连接。
62
+ - [ ] 区分本地删除与服务端撤销授权;本地删除不宣称已撤销服务端设备权限。
63
+
64
+ ### A2. 连接管理与恢复
65
+
66
+ - [ ] 将单一连接管理改为按网关创建连接上下文,包含控制通道、会话通道、请求等待队列和重连任务。
67
+ - [ ] 第一阶段只保持当前网关的业务连接;切换时取消旧订阅与等待请求,并阻止旧回调污染新页面。
68
+ - [ ] 每个网关独立维护未连接、连接中、在线、离线、需要重新配对等状态。
69
+ - [ ] 连接后核对已保存的网关身份;身份不匹配时停止业务操作并提示重新确认,不能自动覆盖原记录。
70
+ - [ ] 新发现或未经确认的地址不直接携带已有 token 探测;制定候选地址信任规则,保留 TLS 校验。
71
+ - [ ] 支持网络恢复后的退避重连与候选地址切换;鉴权失败进入重新配对流程,避免无限重试。
72
+ - [ ] 定义旧网关兼容行为:缺少 `gatewayId` 时使用本地记录 ID 隔离,升级后经可信连接绑定远端身份,不能仅凭名称合并。
73
+
74
+ ### A3. 业务数据隔离
75
+
76
+ - [ ] 会话、工作区、任务、Goal、文件缓存等使用 `(本地网关记录 ID, resourceId)` 作为客户端存储与索引范围。
77
+ - [ ] 所有发送、审批、停止、重命名、归档和文件操作显式携带所属网关上下文。
78
+ - [ ] 页面、订阅、异步回调和待处理请求绑定网关及连接代次,丢弃过期连接的响应。
79
+ - [ ] 切换网关后加载对应配置、模型与能力列表,不复用其他网关的能力判断。
80
+ - [ ] 连接中断后不自动重发发送消息、审批等写操作;结果不确定时先查询状态,避免重复执行。
81
+ - [ ] 明确删除网关时本地缓存、下载文件与会话草稿的处理规则,并在界面说明。
82
+
83
+ ### A4. 界面与验证
84
+
85
+ - [ ] 增加网关列表或切换入口,展示名称、连接状态、当前网关及重新配对入口。
86
+ - [ ] 扫码添加新网关不覆盖已有网关;已存在时明确提示更新。
87
+ - [ ] 会话和审批页面显示所属网关,避免用户向错误机器发送操作。
88
+ - [ ] 增加验证:旧配置迁移、重复配对、切换期间事件到达、跨网关同名同 ID 资源、凭证隔离、撤销后重连、地址与身份不匹配。
89
+
90
+ ## 第二阶段:前台同时连接多个网关
91
+
92
+ ### 网关插件
93
+
94
+ - [ ] 验证仅连接控制通道时的状态同步,以及对审批/提问认领和释放行为的影响。
95
+ - [ ] 验证会话通道按需建立、断开及重建不会影响其他设备或控制通道。
96
+ - [ ] 明确重连后的状态快照与待处理交互恢复约定,有缺口时补充协议或实现。
97
+
98
+ ### App
99
+
100
+ - [ ] 前台为用户启用的多个网关维护控制通道,进入会话时按需连接对应会话通道。
101
+ - [ ] 单个网关连接失败不阻塞其他网关初始化、列表加载或操作。
102
+ - [ ] 如增加聚合任务/会话页面,每项保留来源标识,点击后路由至所属网关。
103
+ - [ ] 限制并发连接与重连频率,处理进入后台、返回前台和网络切换后的状态恢复。
104
+ - [ ] 不将后台常驻 WebSocket 作为可靠通知方案;若需要后台审批通知,另立推送方案待办。
105
+
106
+ ## 第三阶段:局域网自动发现(可选)
107
+
108
+ ### 网关插件
109
+
110
+ - [ ] 增加可选的 mDNS / DNS-SD 广播,约定服务类型、网关 ID、名称、端口、路径与协议版本。
111
+ - [ ] 广播中不包含 token 或配对码;服务关闭时停止广播。
112
+ - [ ] 处理多网卡、地址变化与广播生命周期。
113
+
114
+ ### App
115
+
116
+ - [ ] 实现局域网服务浏览及相应平台权限流程,提供拒绝权限后的扫码/手动添加入口。
117
+ - [ ] 展示“发现但未配对”的网关,发现结果仍需完成配对才能访问业务数据。
118
+ - [ ] 对发现结果去重;广播身份仅用于提示,不据此自动更新可信地址或发送凭证。
119
+
120
+ 跨互联网自动发现不属于本阶段。如果后续需要登录账号后自动列出所有机器,再设计设备目录、登记鉴权、心跳和访问网络方案;目录服务本身不能解决网络不可达问题。
121
+
122
+ ## 实施顺序与验收
123
+
124
+ 1. 先对齐身份字段、地址信任规则与兼容策略,再并行开展网关和 App 实现。
125
+ 2. 完成 G1–G4 与 A1–A4,交付可保存多个网关并可靠切换的版本。
126
+ 3. 再实施前台并行连接,最后按需求增加自动发现。
127
+
128
+ 第一阶段验收清单:
129
+
130
+ - [ ] 同一 App 添加一台服务器和两台 PC,资料及凭证在重启后保留。
131
+ - [ ] 三个网关均可常驻,长时间无客户端后仍能连接;网关重启后身份不变。
132
+ - [ ] 任意切换网关,会话、工作区、模型配置、审批及文件不会串用。
133
+ - [ ] 两个网关返回相同资源 ID 时,缓存和操作仍正确隔离。
134
+ - [ ] 一台机器离线或凭证被撤销,不影响其他网关。
135
+ - [ ] 同一网关更换已确认的访问地址后可恢复连接;不同身份的端点不会被误绑定。
136
+ - [ ] 旧 App 连接新插件、旧单网关配置迁移到新 App 均通过兼容性验证。
137
+ - [ ] 网关插件现有测试与新增行为测试通过,App 完成对应自动化验证及多机联调。
@@ -0,0 +1,264 @@
1
+ {
2
+ "schema_version": 1,
3
+ "diagram_type": "architecture",
4
+ "meta": {
5
+ "title": "DSH Mobile Gateway 运行时架构",
6
+ "subtitle": "移动 App 经网关访问 DSH Host,并接收会话、控制与工作区的实时状态",
7
+ "locale": "zh-CN",
8
+ "output": "runtime-architecture.html",
9
+ "quality_profile": "showcase",
10
+ "visual_preset": "signal-flow",
11
+ "viewBox": [1300, 700],
12
+ "views": [
13
+ {
14
+ "id": "request-path",
15
+ "label": "请求主链路",
16
+ "focus": ["mobileApp", "gatewayIngress", "protocolAdapter", "typertGateway", "dshRuntime"],
17
+ "note": "查看移动请求从 WebSocket 接入到 DSH Runtime 的完整调用链。"
18
+ },
19
+ {
20
+ "id": "realtime-path",
21
+ "label": "实时状态链路",
22
+ "focus": ["dshRuntime", "streamBridge", "gatewayIngress", "mobileApp"],
23
+ "note": "查看会话事件、运行状态与工作区变化如何增量推送回 App。"
24
+ },
25
+ {
26
+ "id": "security-data",
27
+ "label": "安全与数据",
28
+ "focus": ["deviceRegistry", "gatewayIngress", "fileTransfer", "durableState"],
29
+ "note": "查看设备鉴权、Token 摘要、附件和 Host 持久化边界。"
30
+ }
31
+ ]
32
+ },
33
+ "layout": {
34
+ "mode": "grid",
35
+ "origin": [40, 80],
36
+ "cols": 5,
37
+ "gapX": 70,
38
+ "gapY": 120,
39
+ "cellW": 180,
40
+ "cellH": 76
41
+ },
42
+ "components": [
43
+ {
44
+ "id": "mobileApp",
45
+ "type": "frontend",
46
+ "label": "移动端 App",
47
+ "sublabel": "控制通道 + 会话通道",
48
+ "pos": [40, 330],
49
+ "size": [150, 76]
50
+ },
51
+ {
52
+ "id": "dshWebUi",
53
+ "type": "frontend",
54
+ "label": "DSH WebUI",
55
+ "sublabel": "同进程管理界面",
56
+ "pos": [40, 80],
57
+ "size": [150, 76]
58
+ },
59
+ {
60
+ "id": "gatewayIngress",
61
+ "type": "cloud",
62
+ "label": "网关接入",
63
+ "sublabel": "WSS / LAN · /ws/mobile",
64
+ "tag": "Nginx 可选",
65
+ "pos": [250, 330],
66
+ "size": [180, 76]
67
+ },
68
+ {
69
+ "id": "deviceRegistry",
70
+ "type": "security",
71
+ "label": "设备认证",
72
+ "sublabel": "配对码 · Token 摘要",
73
+ "pos": [250, 80],
74
+ "size": [180, 76]
75
+ },
76
+ {
77
+ "id": "protocolAdapter",
78
+ "type": "backend",
79
+ "label": "协议路由与适配",
80
+ "sublabel": "控制 / 会话 → Host 调用",
81
+ "pos": [500, 330],
82
+ "size": [180, 76]
83
+ },
84
+ {
85
+ "id": "fileTransfer",
86
+ "type": "backend",
87
+ "label": "附件传输",
88
+ "sublabel": "上传 · 下载 · 工作区文件",
89
+ "pos": [250, 500],
90
+ "size": [180, 76]
91
+ },
92
+ {
93
+ "id": "streamBridge",
94
+ "type": "messagebus",
95
+ "label": "实时状态桥",
96
+ "sublabel": "SessionEvent · control · workspace",
97
+ "pos": [500, 500],
98
+ "size": [180, 76]
99
+ },
100
+ {
101
+ "id": "typertGateway",
102
+ "type": "backend",
103
+ "label": "Remote Gateway",
104
+ "sublabel": "invoke / stream",
105
+ "pos": [750, 330],
106
+ "size": [180, 76]
107
+ },
108
+ {
109
+ "id": "dshRuntime",
110
+ "type": "backend",
111
+ "label": "DSH Runtime",
112
+ "sublabel": "Session · Workspace · HITL",
113
+ "pos": [1000, 330],
114
+ "size": [180, 76]
115
+ },
116
+ {
117
+ "id": "durableState",
118
+ "type": "database",
119
+ "label": "本地持久化",
120
+ "sublabel": "设备 · 附件 · Session 历史",
121
+ "pos": [1000, 500],
122
+ "size": [180, 76]
123
+ }
124
+ ],
125
+ "boundaries": [
126
+ {
127
+ "kind": "region",
128
+ "label": "客户端",
129
+ "wraps": ["mobileApp", "dshWebUi"],
130
+ "pad": 24
131
+ },
132
+ {
133
+ "kind": "region",
134
+ "label": "Mobile Gateway 插件进程",
135
+ "wraps": ["gatewayIngress", "deviceRegistry", "protocolAdapter", "fileTransfer", "streamBridge"],
136
+ "pad": 28
137
+ },
138
+ {
139
+ "kind": "region",
140
+ "label": "DSH Host",
141
+ "wraps": ["typertGateway", "dshRuntime", "durableState"],
142
+ "pad": 28
143
+ }
144
+ ],
145
+ "connections": [
146
+ {
147
+ "id": "mobileTransport",
148
+ "from": "mobileApp",
149
+ "to": "gatewayIngress",
150
+ "label": "WS / WSS",
151
+ "variant": "emphasis",
152
+ "fromSide": "right",
153
+ "toSide": "left",
154
+ "labelAt": [220, 352]
155
+ },
156
+ {
157
+ "id": "webUiManagement",
158
+ "from": "dshWebUi",
159
+ "to": "gatewayIngress",
160
+ "label": "管理 API",
161
+ "fromSide": "bottom",
162
+ "toSide": "top",
163
+ "via": [[115, 230], [340, 230]]
164
+ },
165
+ {
166
+ "id": "deviceAuth",
167
+ "from": "deviceRegistry",
168
+ "to": "gatewayIngress",
169
+ "label": "鉴权",
170
+ "variant": "security",
171
+ "fromSide": "bottom",
172
+ "toSide": "top",
173
+ "labelAt": [340, 170]
174
+ },
175
+ {
176
+ "id": "protocolFrames",
177
+ "from": "gatewayIngress",
178
+ "to": "protocolAdapter",
179
+ "variant": "emphasis",
180
+ "fromSide": "right",
181
+ "toSide": "left"
182
+ },
183
+ {
184
+ "id": "hostCalls",
185
+ "from": "protocolAdapter",
186
+ "to": "typertGateway",
187
+ "variant": "emphasis",
188
+ "fromSide": "right",
189
+ "toSide": "left"
190
+ },
191
+ {
192
+ "id": "remoteContract",
193
+ "from": "typertGateway",
194
+ "to": "dshRuntime",
195
+ "variant": "emphasis",
196
+ "fromSide": "right",
197
+ "toSide": "left"
198
+ },
199
+ {
200
+ "id": "persistHostState",
201
+ "from": "dshRuntime",
202
+ "to": "durableState",
203
+ "label": "历史 / 工作区",
204
+ "fromSide": "bottom",
205
+ "toSide": "top",
206
+ "labelAt": [1090, 430]
207
+ },
208
+ {
209
+ "id": "emitRuntimeState",
210
+ "from": "dshRuntime",
211
+ "to": "streamBridge",
212
+ "label": "事件 / 状态",
213
+ "variant": "dashed",
214
+ "fromSide": "left",
215
+ "toSide": "right",
216
+ "via": [[950, 368], [950, 538]]
217
+ },
218
+ {
219
+ "id": "pushMobileState",
220
+ "from": "streamBridge",
221
+ "to": "gatewayIngress",
222
+ "label": "增量推送",
223
+ "variant": "dashed",
224
+ "fromSide": "top",
225
+ "toSide": "bottom",
226
+ "via": [[590, 455], [340, 455]]
227
+ },
228
+ {
229
+ "id": "readWorkspace",
230
+ "from": "fileTransfer",
231
+ "to": "durableState",
232
+ "label": "文件 I/O",
233
+ "fromSide": "bottom",
234
+ "toSide": "bottom",
235
+ "via": [[340, 650], [1090, 650]]
236
+ }
237
+ ],
238
+ "cards": [
239
+ {
240
+ "dot": "cyan",
241
+ "title": "请求路径",
242
+ "items": [
243
+ "App 经 WSS 或局域网接入",
244
+ "网关鉴权、分流并调用 Host"
245
+ ]
246
+ },
247
+ {
248
+ "dot": "emerald",
249
+ "title": "实时路径",
250
+ "items": [
251
+ "订阅 SessionEvent 与控制状态",
252
+ "按连接向 App 增量推送"
253
+ ]
254
+ },
255
+ {
256
+ "dot": "amber",
257
+ "title": "安全与数据",
258
+ "items": [
259
+ "设备 Token 仅持久化摘要",
260
+ "附件与 Session 历史分层管理"
261
+ ]
262
+ }
263
+ ]
264
+ }