@sparkelf/dsh-plugin-mobile-gateway 0.8.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.
@@ -0,0 +1,124 @@
1
+ # 多网关第一阶段:App 对接说明
2
+
3
+ 本文对应 Gateway 插件本次源码实现;尚未发布新的 npm 版本。App 第一阶段目标为保存多个网关并切换连接,暂不要求同时保持多个控制通道。无需中央服务器,也不包含自动发现。
4
+
5
+ ## 1. 不变的协议
6
+
7
+ - WebSocket 路径默认 `/ws/mobile`,子协议仍为 `dsh-mobile-v1`,`hello.protocol` 仍为 `3`。
8
+ - 配对载荷仍为 `version: 2` 的 JSON,经 UTF-8、无 padding Base64URL 编码。
9
+ - 首次连接发送子协议 `dsh-mobile-v1, dsh-pair.<pairingCode>`,同时发送 `X-DSH-Device-ID` 安装级 ID。
10
+ - 配对成功只发送一次 `paired.token`;后续使用 `Authorization: Bearer <token>` 或 `dsh-auth.<token>` 子协议。
11
+ - 控制通道发送 `X-DSH-Channel: control`;取得 token 并确认控制通道的 `hello` 后,会话通道使用同一网关 token 与 `X-DSH-Channel: conversation`。
12
+ - App 不调用 `/mgw/*` 管理接口。这些接口仅供 DSH 本机管理界面使用。
13
+
14
+ ## 2. 新增字段
15
+
16
+ 以下字段均为向后兼容的增量字段。新版 App 对旧插件应允许字段缺失,但新版插件返回的身份必须校验格式及一致性。
17
+
18
+ | 字段 | 类型 | 返回位置 | 含义 |
19
+ |---|---|---|---|
20
+ | `gatewayId` | string,UUID v4 | 配对载荷、`paired`、`hello`、管理状态 | 网关安装实例身份,重启和改地址不变 |
21
+ | `gatewayName` | string | 同上 | 服务端展示名称,可变,不用于合并资料或鉴权 |
22
+ | `endpoints` | string[] | 配对载荷、管理状态 | 同一网关的候选地址,规范化后去重,合并最多 16 个 |
23
+ | `gatewayMode` | string | 管理状态及模式修改响应 | `disabled` / `temporary` / `persistent`;不参与移动业务帧 |
24
+
25
+ `hello` 和 `paired` 不返回地址列表。新增可信地址需重新扫码或经用户明确确认,不能从未认证的广播或错误跳转中自动吸收。
26
+
27
+ 配对载荷示例(这里展示的是解码后的 JSON,不能将原始 JSON 直接交给现有扫码解析器):
28
+
29
+ ```json
30
+ {
31
+ "version": 2,
32
+ "publicUrl": "wss://gateway.example.com/ws/mobile",
33
+ "pairingCode": "<一次性配对码>",
34
+ "expiresAt": 4102444800000,
35
+ "gatewayId": "d56a1098-8519-43a1-9dce-fb99863bf5bb",
36
+ "gatewayName": "家里电脑",
37
+ "endpoints": [
38
+ "wss://gateway.example.com/ws/mobile",
39
+ "ws://192.168.1.10:3081/ws/mobile"
40
+ ]
41
+ }
42
+ ```
43
+
44
+ 成功后收到:
45
+
46
+ ```json
47
+ {
48
+ "kind": "paired",
49
+ "token": "<仅返回一次的长期 token>",
50
+ "device": { "id": "<此网关分配的设备 ID>", "name": "Phone" },
51
+ "gatewayId": "d56a1098-8519-43a1-9dce-fb99863bf5bb",
52
+ "gatewayName": "家里电脑"
53
+ }
54
+ ```
55
+
56
+ 之后 `hello` 保留原字段,并增加相同的 `gatewayId`、`gatewayName`。所有业务请求仍按原协议发送,不需要把 `gatewayId` 加入每条消息;App 根据连接上下文路由。
57
+
58
+ ## 3. 地址语义与信任规则
59
+
60
+ `publicUrl` 是本次配对的首选地址,兼容旧 App。`endpoints` 的来源顺序为:首选地址 → 本次本机配对请求指定的地址 → 插件配置地址 → 已配置公网地址 → 已启动局域网监听的地址。管理状态没有本次配对地址,只返回配置与监听产生的候选地址。
61
+
62
+ - `http` / `https` 输入由插件规范化为 `ws` / `wss`。不允许用户名密码、查询参数、fragment 或 `0.0.0.0` / `::` 监听地址;公网明文 WS 被拒绝。
63
+ - 每个输入列表最多 16 项,每项最多 2048 字符;合并后超过 16 个也会拒绝。候选地址可能包含手机不可达的 loopback 地址,App 应过滤或提示,不可据此判断网关离线。
64
+ - 地址顺序不保证网络可达或代表实时延迟;App 可以优先尝试最近成功且已确认可信的地址。
65
+ - 只有来自用户认可的配对二维码/手动导入,或用户单独确认的地址,才可使用凭证。`gatewayId` 是关联标识,不是证书或密码学身份凭据。
66
+ - `hello` 在鉴权之后返回,不能依赖“先发送 token、再比较 ID”保护 token 不被恶意端点窃取;TLS 校验和发送前的地址信任判断必须先完成。局域网明文 WS 仅用于用户信任的网络。
67
+ - 地址被重定向时不得把 token 自动转发到其他来源。
68
+ - 配对码是单次使用。候选地址上的配对请求必须串行;若发生网络断开且无法确定配对是否已被消费,应重新生成二维码,不能对多个地址并行消耗同一码。
69
+
70
+ ## 4. 客户端状态与接入流程
71
+
72
+ 建议保存:
73
+
74
+ ```text
75
+ GatewayProfile
76
+ localId 本地稳定记录 ID
77
+ gatewayId? 远端身份,旧插件允许缺失
78
+ gatewayName 服务端名称
79
+ alias? 用户本地别名
80
+ endpoints[] 已确认的候选地址
81
+ preferredEndpoint? 最近成功地址
82
+ credentialRef 按 localId 隔离的安全存储引用
83
+ remoteDeviceId? 此网关返回的 device.id
84
+ lastConnectedAt?
85
+ ```
86
+
87
+ 1. 将旧单网关配置迁移为一条资料;保留 token、缓存和草稿,迁移要幂等。未配对成功的新资料不覆盖当前网关。
88
+ 2. 解析配对载荷并检查版本、有效期、地址和身份字段。已有相同 `gatewayId` 时提示更新资料,不按名称或 IP 合并。
89
+ 3. 建立控制通道;收到 `paired` 时核对其身份与二维码相符,再立即将 token 写入该资料的安全存储。持久化失败时阻止后续业务,提示重新配对。
90
+ 4. 收到 `hello` 后再次核对身份,保存服务端名称及原协议能力列表。二维码带有身份时,缺失或不一致的返回均视为异常,不能降级为旧网关。
91
+ 5. 根据页面需要使用该网关 token 建立会话通道,再核对其 `hello.gatewayId`。
92
+ 6. 重连仅使用该资料的已确认地址与凭证。网关 ID 不匹配时关闭连接、保留原记录并提示重新确认。
93
+ 7. 再次配对同一安装会轮换该网关的 token。保存新 token 后主动重建该网关现有通道;不影响其他网关。
94
+
95
+ 旧插件完全没有身份字段时,以 `localId` 隔离资料。升级后只经原本可信且认证成功的连接绑定远端 ID,遇到记录冲突须提示,不能静默合并。
96
+
97
+ 第一阶段切换网关时应关闭旧连接、取消订阅与待处理请求。连接代次和网关 ID 一起绑定到异步回调,丢弃旧回调。数据键使用 `(localId, resourceId)`,模型、工作区、会话、任务、审批和文件均需隔离。
98
+
99
+ 发送消息、审批、停止任务等写操作超时后,不自动重发;查询状态后再决定操作,避免重复执行。删除本地网关不等同于撤销服务端授权。
100
+
101
+ ## 5. 错误与状态处理
102
+
103
+ | 结果 | App 行为 |
104
+ |---|---|
105
+ | Upgrade HTTP 503 | 显示网关关闭或不可用;有界退避,提示本机开启 |
106
+ | Upgrade HTTP 401 | 凭证无效、配对过期或已使用;停止用同一凭证无限重试,提供重新配对 |
107
+ | Upgrade HTTP 400 | 检查安装级 ID 等协议参数,显示可理解的错误 |
108
+ | close 4003 | 可能是撤销设备或重新开启鉴权;重新检查授权,不能仅显示普通网络中断 |
109
+ | close 4004 | 显示网关已关闭,避免紧密重连循环 |
110
+ | 网络断开 | 当前资料独立退避重连,不将其他网关标记离线 |
111
+ | 返回身份不匹配 | 停止业务,不覆盖既有身份、地址或 token |
112
+
113
+ ## 6. 已核对的 App 代码入口
114
+
115
+ 在兄弟仓库中只读检查了以下入口,本次没有修改 App:
116
+
117
+ - `DeepSeekHarnessMobile/Core/GatewayModels.swift`:`GatewayPairingPayload` 当前只有四个旧字段;需要增加可选字段,并在业务帧模型补充身份。
118
+ - `DeepSeekHarnessMobile/Core/PairingPayloadParser.swift`:当前严格检查 Base64URL、version 2 和过期时间;应保留原校验并增加新字段规则。
119
+ - `DeepSeekHarnessMobile/Core/GatewayFrameRouter.swift`:`paired` / `hello` 路由需传递网关身份,绑定所属连接上下文。
120
+ - `DeepSeekHarnessMobile/Core/AppStore.swift`:配对入口需要从单网关状态迁移到按资料管理。其余连接、缓存及安全存储调用点需在 App 实施时继续排查。
121
+
122
+ 现有 Codable 解码器会忽略新增字段,已用当前真实配对模型与解析器执行兼容性样例;这不代表 App 已实现多网关,也不能代替真机验收。
123
+
124
+ App 交付验收以 [第一阶段验收报告](multi-gateway-phase1-acceptance.md) 的人工步骤及 [待办](multi-gateway-todo.md) A1–A4 为准。
@@ -0,0 +1,179 @@
1
+ # 多网关第一阶段:Gateway 验收报告
2
+
3
+ ## 结论与范围
4
+
5
+ 2026-09-09:第一阶段 Gateway 插件能力已实现,自动化回归通过。多网关待办 G1–G4 已勾选;App A1–A4、第二/三阶段及真实多机人工验收保持未完成。
6
+
7
+ 本次只修改插件仓库,未修改 App,未部署到用户的服务器/PC,未发布 npm 新版本。当前 package.json 仍为 0.7.2,因此验收应使用本次工作区构建/打包产物,不能直接用 npm 上的同版本发布包推断已包含改动。
8
+
9
+ ## 交付内容
10
+
11
+ | 能力 | 实现位置 | 结果 |
12
+ |---|---|---|
13
+ | 三种模式、界面选择、状态持久化及启动优先级 | `lib/index.mjs`、`lib/client.js`、`lib/gateway-state.mjs` | 已实现 |
14
+ | 稳定网关 UUID、配置名称、损坏时拒绝启动 | `lib/gateway-state.mjs`、`lib/index.mjs` | 已实现 |
15
+ | 配对候选地址合并、去重、URL 校验 | `lib/index.mjs` | 已实现 |
16
+ | 配对/paired/hello 身份扩展及旧协议保留 | `lib/index.mjs`、`PROTOCOL.md` | 已实现 |
17
+ | App 字段、迁移、路由及安全存储约定 | [App 对接说明](multi-gateway-app-integration.md) | 已交付文档;App 尚未实现 |
18
+ | 实施范围及勾选状态 | [多网关待办](multi-gateway-todo.md) | 仅勾选已完成的 Gateway 项 |
19
+
20
+ ## 自动化验证
21
+
22
+ 执行环境:macOS arm64、Node.js v24.19.0;兼容性样例使用 Apple Swift 6.3.3。集成测试启动本地 HTTP/WebSocket 服务,DSH Host 使用测试替身,未连接真实 DSH 服务。测试因沙箱禁止监听本机端口,使用经批准的沙箱外执行完成。
23
+
24
+ | 验证 | 结果 | 证据/范围 |
25
+ |---|---|---|
26
+ | `npm test` | 通过 | setup-ip、host-adapter、gateway、auth、lan、multi-gateway 六个测试脚本 |
27
+ | 原业务分发回归 | 通过 | `FULL GATEWAY DISPATCH TESTS PASSED (115)` |
28
+ | 新增多网关验证 | 通过 | `multi-gateway: 11 checks passed`,见下方明细 |
29
+ | LAN 与主监听身份一致 | 通过 | LAN 配对取得 token 后连接本机监听,比较 gatewayId;验证两入口仍按原规则鉴权 |
30
+ | 旧 iOS 配对解码兼容 | 通过 | 从兄弟仓库提取实际 `GatewayPairingPayload` 和 `PairingPayloadParser`,使用 Swift 执行带 gatewayId/gatewayName/endpoints 的 v2 样例 |
31
+ | JavaScript 语法与补丁空白检查 | 通过 | `node --check` 检查修改后的 JS/MJS;`git diff --check` |
32
+ | 真机扫码、真实界面布局及真实多机联调 | 未执行 | 按下方步骤人工验收 |
33
+
34
+ 新增 11 组行为检查:
35
+
36
+ 1. Schema 模式校验、身份及模式持久化、文件权限 0600、损坏文件拒绝加载且保留原文件。
37
+ 2. 旧启动配置 true 仍表示常驻;不同实例 ID 不同。
38
+ 3. 主动关闭后连接返回 503,重启及更改启动配置不会覆盖已保存关闭状态;非法请求返回 400。
39
+ 4. 旧 enabled 接口映射临时模式,临时模式重启重新计时,自动关闭结果持久化。
40
+ 5. 切换常驻取消旧计时器,重启后模式与身份保持;名称变化不改变身份。
41
+ 6. v2 Base64URL 配对载荷保留旧字段,新增地址规范化/去重;非法地址和过量列表返回 400。
42
+ 7. 配对载荷、paired、控制 hello、会话 hello 身份一致,后续 token 连接不重复发送 paired。
43
+ 8. 有客户端时切换临时模式不会设置等待计时器,断线后保持开启;重新配对轮换 token 并复用本机设备记录。
44
+ 9. 同一客户端安装 ID 在两个网关分别配对,token 不能跨网关使用;撤销 A 不影响 B。
45
+ 10. 主动关闭断开现有 WebSocket,close code 为 4004。
46
+ 11. 模拟持久化失败返回 500,当前运行模式不被改变。
47
+
48
+ 说明:临时超时测试直接调用插件时使用 120ms 等短测试值以缩短执行时间;真实 Cordis Schema 最小值仍为 30000ms。常驻测试证明其不会被等待首次连接的计时器关闭,不代表完成了长时间运行稳定性或压力测试。
49
+
50
+ 复验命令(在插件仓库执行):
51
+
52
+ ```bash
53
+ npm test
54
+ node --check lib/index.mjs
55
+ node --check lib/client.js
56
+ node --check lib/gateway-state.mjs
57
+ git diff --check
58
+ ```
59
+
60
+ ## 人工验收准备
61
+
62
+ 1. 准备两台可访问的机器 A/B;完整目标验收再增加第三台 C(例如一台服务器、两台 PC)。每台运行包含本次改动的插件及兼容 DSH。
63
+ 2. 使用独立的测试设备注册文件和 `gatewayStateFile`;不要与日常使用实例共用文件。给每台配置不同的 `gatewayName`。
64
+ 3. 为测试实例设置 `gatewayWaitTimeoutMs: 30000`,保留 `requireAuth: true`;实际超时等待 35 秒。测试结束恢复所需生产值。
65
+ 4. 打开各自本机 WebUI 的“移动设备”。准备旧版 App 做兼容验收;多网关客户端步骤需等待 App 按对接说明实现。
66
+ 5. 若使用公网入口,先准备有效 WSS 和手机可达的网络。不要为了测试把 `/mgw/*` 管理接口开放到公网。
67
+ 6. 记录插件来源、DSH/App 版本、机器与网络、执行时间。下方全部人工项尚未执行,应由验收者填写实测结果。
68
+
69
+ 可在运行该实例的电脑上检查状态。默认示例端口为 3080,实际端口不同时替换:
70
+
71
+ ```bash
72
+ curl --fail-with-body http://127.0.0.1:3080/mgw/status
73
+ ```
74
+
75
+ ### M1. 常驻选择、无连接等待与重启
76
+
77
+ - [ ] 已人工通过
78
+
79
+ 操作:所有手机断开 → 面板选择“常驻开启” → 读取状态并记录 gatewayId → 等待 35 秒 → 再读取状态 → 停止并重启测试 DSH → 再读取状态。
80
+
81
+ 预期:每次 `gatewayMode=persistent`、`gatewayEnabled=true`、`waitExpiresAt=null`,gatewayId 不变;面板仍显示常驻。重新扫码或使用已有可信凭证可连接。
82
+
83
+ ### M2. 主动关闭持久化
84
+
85
+ - [ ] 已人工通过
86
+
87
+ 操作:手机保持连接 → 面板选择“关闭” → 观察手机断线 → 读取状态 → 保持启动配置为 persistent,重启 DSH → 再连接。
88
+
89
+ 预期:连接关闭码为 4004;状态 disabled/false/null;重启后仍关闭,重新连接 Upgrade 返回 503。面板改回常驻后恢复。
90
+
91
+ ### M3. 临时模式的三个分支
92
+
93
+ - [ ] 已人工通过
94
+
95
+ 操作 A:没有手机连接时选临时开启,等待 35 秒,再重启 DSH。
96
+
97
+ 预期 A:先显示等待截止时间,超时自动变关闭,重启仍关闭。
98
+
99
+ 操作 B:选临时开启,在 30 秒内完成手机连接,再断开手机,等待 35 秒。
100
+
101
+ 预期 B:首次连接清除等待计时器,断开后本次运行仍开启。
102
+
103
+ 操作 C:在 B 的状态重启 DSH,确保手机关闭自动重连,等待 35 秒。
104
+
105
+ 预期 C:重启后临时模式重新计时,未连接则自动关闭。再次开启临时后立即切为常驻,等待 35 秒,确认旧计时器没有将常驻关闭。
106
+
107
+ ### M4. 协议身份及旧 App 兼容
108
+
109
+ - [ ] 已人工通过
110
+
111
+ 操作:在 A 面板生成新二维码并复制配对字符串;检查解码结果为 version 2,包含原四字段及 gatewayId/gatewayName/endpoints。用旧版 App 扫码,查看工作区、会话并发一条测试消息。
112
+
113
+ 预期:旧版 App 能扫码并正常完成原有业务;服务端使用原子协议和 hello protocol 3。用带帧调试能力的测试客户端核对 paired、control hello、conversation hello 与二维码中的 gatewayId 全部相同。不要把配对码或 token 贴入验收记录。
114
+
115
+ ### M5. 同一网关多个地址
116
+
117
+ - [ ] 已人工通过
118
+
119
+ 操作:给 A 配置已实际可达的 LAN 和 WSS 地址;重新启动后生成配对码。用测试客户端或新版 App 完成首次配对,然后以所得同一 token 分别连接两个地址。
120
+
121
+ 预期:二维码保留所选 publicUrl,候选地址去重;两地址返回同一 gatewayId。LAN 地址不可达时只影响该地址,不生成另一条网关身份。
122
+
123
+ 反例:在本机配对请求传入下方错误地址,应返回 HTTP 400,不生成可用二维码:
124
+
125
+ ```bash
126
+ curl -i http://127.0.0.1:3080/mgw/pair \
127
+ -H 'Content-Type: application/json' \
128
+ --data '{"publicUrl":"wss://0.0.0.0/ws/mobile"}'
129
+ ```
130
+
131
+ ### M6. 不同网关凭证及撤销隔离
132
+
133
+ - [ ] 已人工通过
134
+
135
+ 操作:测试客户端使用同一个安装级 `X-DSH-Device-ID` 分别配对 A、B → 确认 ID 不同 → 使用 A token 连接 B(反向也测试)→ 用各自 token 正常连接 → 在 A 面板撤销测试设备。
136
+
137
+ 预期:跨网关 token 均返回 401;A 连接以 4003 关闭且旧 token 不再可用;B 连接、token、业务访问不受影响。
138
+
139
+ ### M7. 名称、配置和文件故障
140
+
141
+ - [ ] 已人工通过
142
+
143
+ 操作 A:记录 A 身份,修改 gatewayName 与候选地址配置,重启。
144
+
145
+ 预期 A:名称和地址更新,gatewayId 不变。
146
+
147
+ 操作 B(仅独立测试实例):停止 DSH,备份其状态文件,临时写入无效 JSON,再启动;随后停止失败实例、恢复原备份并重启。
148
+
149
+ 预期 B:损坏时明确报 `failed to load gateway state`,没有静默覆盖文件或换 ID;恢复后原身份仍可用。
150
+
151
+ 操作 C:在独立测试实例模拟状态文件不可写,修改运行模式。
152
+
153
+ 预期 C:管理请求返回 500,界面显示失败,当前模式不变。恢复写权限后可以正常保存。若模拟的是自动超时保存失败,当前进程仍关闭并记录错误;应修复存储并重新保存,不能据此保证重启后的模式。
154
+
155
+ ### M8. 多网关 App 验收(待 App 实现)
156
+
157
+ - [ ] 已人工通过
158
+
159
+ 操作:新版 App 依次添加 A/B/C → 重启 App → 在三台网关之间切换 → 每台创建不同测试会话并检查工作区、模型配置、审批、文件 → 让 A 离线再切到 B/C → 再次扫描 B 的二维码更新配对。
160
+
161
+ 预期:资料及凭证保留;会话及操作始终属于正确网关;A 离线不影响 B/C;重复扫码更新 B 而不是新增重复资料或覆盖 A/C。进一步构造跨网关相同资源 ID 和切换时旧事件迟到,验证缓存和回调隔离。
162
+
163
+ 本项不是 Gateway 单仓库可完成的验收,本报告不勾选。
164
+
165
+ ### M9. 未知地址与身份不匹配(待 App 实现)
166
+
167
+ - [ ] 已人工通过
168
+
169
+ 操作:在独立测试环境让 A 资料指向 B 或一个不可信新地址,尝试恢复连接;再验证同一 gatewayId 的正常地址更新流程。
170
+
171
+ 预期:App 不自动向未经确认的地址发送凭证;可信地址返回错误身份时停止业务且保留原记录。不能通过同名或自报 gatewayId 绕过确认与 TLS 校验。
172
+
173
+ ## 人工记录模板
174
+
175
+ | 编号 | 实际结果 | 通过/失败/未执行 | 证据(脱敏) | 执行人/时间 |
176
+ |---|---|---|---|---|
177
+ | M1–M9(每项独立填写) | 待填写 | 未执行 | 待填写 | 待填写 |
178
+
179
+ 发布前至少完成 M1–M7。宣称 App 第一阶段整体完成前,还需完成 A1–A4 及 M8–M9。后台推送、并行控制连接、mDNS 发现、中心目录和网络中转不在本次交付范围。
@@ -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,52 @@
1
+ # Remote Gateway 重构实施计划
2
+
3
+ ## 目标
4
+
5
+ 在保持移动端 `dsh-mobile-v1`、配对流程和现有消息字段不变的前提下,移除对 DSH `APIProxy` 的依赖,改为只通过 Host 侧 `typertGateway` 访问 DSH v0.1.2-rc.1 Remote API。所有上游协议差异集中在插件内部适配层,后续 DSH 发生破坏性更新时不要求移动端同步升级。
6
+
7
+ ## 稳定边界
8
+
9
+ - 对移动端:继续协商 `dsh-mobile-v1`,`hello.protocol` 保持 `3`,已有请求和响应帧保持兼容。
10
+ - 对 DSH:仅 `lib/dsh-host-adapter.mjs` 知道 Remote namespace、method、严格参数名、stream opening frame 和错误形态。
11
+ - 对业务编排:`lib/index.mjs` 只调用语义化 Host Adapter,不拼装 Remote 描述符。
12
+ - 对错误:上游带 `code/message` 的错误映射为既有移动端错误帧;未知异常稳定映射为 `internal`。
13
+ - 对历史:适配层把 RC 版压缩的 `chunkrow/*` 记录还原为 v1 已支持的 `assistant/chunk` 事件。
14
+
15
+ ## 实施阶段
16
+
17
+ ### 1. Host Adapter 与普通 RPC
18
+
19
+ - 封装 `invoke()` / `stream()`,校验关键返回值。
20
+ - 迁移 Session、Workspace、Settings、Commands、Skills、Agent Presets、LLM、Goals。
21
+ - Session 历史通过 `session.follow` 获取快照游标和 projections;旧页通过 `session.page` 读取。
22
+ - Workspace 列表通过 `workspace.follow` 的 baseline 获取。
23
+ - Host 信息由插件本地能力和 Remote catalog 合成,不再依赖已删除的 `host.describe`。
24
+
25
+ ### 2. 实时与 Human-in-the-loop
26
+
27
+ - 普通实时会话事件继续使用 Host 内部 `session/event`,保持移动端事件帧不变。
28
+ - projection 更新从 `session.control` stream 转发,替代旧 `api.events.mux()` 中的投影帧。
29
+ - approval/question 改为 Cordis waterfall listener;生成插件自有不透明 `rpcId`,等待移动端回答。
30
+ - 只有存在匹配会话的已认证移动连接时才认领 waterfall;否则立即 `next()`,保留 WebUI 回退路径。
31
+ - 连接断开、请求 signal 取消或插件卸载时释放等待者并回退/取消,禁止悬挂 Promise。
32
+
33
+ ### 3. 兼容性测试与门禁
34
+
35
+ - 使用假的 `typertGateway`,按 RC 的精确 namespace/method/args 测试,不再模拟 APIProxy。
36
+ - 添加 Host Adapter 单元测试,覆盖 direct value、throw、stream baseline、历史 chunk 展开和严格参数形状。
37
+ - 保留现有 WebSocket v1 端到端用例,验证移动端请求/响应字段没有改变。
38
+ - 增加静态门禁:生产代码不得出现 `apiProxy`、旧 `{ rpcId, payload }` Remote 调用或 `api.events.mux()`。
39
+
40
+ ### 4. 配置与文档
41
+
42
+ - 删除 `apiProxy` 注入声明。
43
+ - 更新 README、PROTOCOL 和 bundle patch 注释,明确移动协议与 DSH Remote 的分层。
44
+ - 保留 `session-query-sqlite` 的按需搜索配置,但将其标记为可选能力,避免它成为插件启动前提。
45
+
46
+ ## 验收标准
47
+
48
+ 1. 插件可在 DSH v0.1.2-rc.1 组合中加载,且不请求 `apiProxy`。
49
+ 2. 当前移动端无需修改即可完成配对、会话列表、历史、发送消息、命令、模型、设置、任务、Goal、文件和交互审批。
50
+ 3. 全量测试通过;Remote 调用参数均由适配层测试锁定。
51
+ 4. 上游 namespace 或数据结构未来变化时,改动范围原则上限定于 Host Adapter 及对应契约测试。
52
+