dsh-plugin-mobile-gateway 0.6.9 → 0.7.1
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.
- package/PROTOCOL.md +113 -22
- package/README.md +11 -1
- package/cordis.patch.yml +2 -2
- package/docs/blog-mobile-gateway.md +1 -1
- package/docs/remote-gateway-refactor-plan.md +52 -0
- package/lib/dsh-host-adapter.mjs +304 -0
- package/lib/index.mjs +376 -224
- package/package.json +3 -3
package/PROTOCOL.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dsh Mobile Gateway — WebSocket 协议参考
|
|
2
2
|
|
|
3
|
-
移动端通过一个经过设备鉴权的 WebSocket 连接与 dsh 通信:订阅 agent 实时输出、发送文字和图片、处理 Human-in-the-loop 提问与操作审批、查询会话/工作区/历史、调整会话配置。本协议由持久化插件 `dsh-plugin-mobile-gateway` 实现(v0.
|
|
3
|
+
移动端通过一个经过设备鉴权的 WebSocket 连接与 dsh 通信:订阅 agent 实时输出、发送文字和图片、处理 Human-in-the-loop 提问与操作审批、查询会话/工作区/历史、调整会话配置。本协议由持久化插件 `dsh-plugin-mobile-gateway` 实现(v0.7.1)。
|
|
4
4
|
|
|
5
5
|
- **本机端点**:`ws://127.0.0.1:3080/ws/mobile`(与 dsh web GUI 同端口)
|
|
6
6
|
- **局域网端点**:`ws://<电脑的私有局域网 IP>:3081/ws/mobile`(插件独立监听,只提供经过鉴权的 WebSocket)
|
|
@@ -77,7 +77,7 @@ const pairingText = Buffer.from(JSON.stringify(payload), 'utf8').toString('base6
|
|
|
77
77
|
```json
|
|
78
78
|
{ "kind": "paired", "token": "<长期设备 token>",
|
|
79
79
|
"device": { "id": "...", "name": "iPhone", "createdAt": 1787111700000 } }
|
|
80
|
-
{ "kind": "hello", "protocol": 3, "capabilities": ["images", "file-downloads"], "authenticated": true,
|
|
80
|
+
{ "kind": "hello", "protocol": 3, "capabilities": ["images", "commands", "tasks", "goals", "file-downloads"], "authenticated": true,
|
|
81
81
|
"device": { "id": "...", "name": "iPhone" }, "port": 3080, "clients": 1 }
|
|
82
82
|
```
|
|
83
83
|
|
|
@@ -162,11 +162,11 @@ Human-in-the-loop 分为两条独立通道:
|
|
|
162
162
|
- **提问**:Agent 的 `ask_user_question` 工具向用户收集答案。
|
|
163
163
|
- **审批**:高风险工具操作(例如沙箱升权)请求一次性允许或拒绝。
|
|
164
164
|
|
|
165
|
-
二者都是
|
|
165
|
+
二者都是 Host waterfall 的临时请求,不属于持久化的 `session/event`,且都必须以插件为该次请求生成的 `rpcId` 通过专用响应帧回答,不能作为普通 `message` 发送。该内部实现不改变移动端帧格式。
|
|
166
166
|
|
|
167
167
|
### 3.1 提问与回答
|
|
168
168
|
|
|
169
|
-
Agent 调用 DSH 的 `ask_user_question`
|
|
169
|
+
Agent 调用 DSH 的 `ask_user_question` 工具时,插件直接接入 Host 的 `user-questions/request` waterfall,并把临时请求投影给移动端。若没有可处理该 Session 的移动连接,插件调用 `next()`,由 WebUI 或后续 Host answerer 处理。
|
|
170
170
|
|
|
171
171
|
#### `question-requested` — 服务端推送问题
|
|
172
172
|
|
|
@@ -191,7 +191,7 @@ Agent 调用 DSH 的 `ask_user_question` 工具时,插件通过 API Gateway
|
|
|
191
191
|
}
|
|
192
192
|
```
|
|
193
193
|
|
|
194
|
-
- `rpcId
|
|
194
|
+
- `rpcId`:移动网关为这一整批问题生成的不透明稳定 ID。回答或取消时必须原样返回,客户端不得自行生成或解析。
|
|
195
195
|
- `questions`:一次工具调用中的完整问题批次;可能包含多题。
|
|
196
196
|
- `id`:问题 ID,必须在对应答案中原样返回。
|
|
197
197
|
- `header` / `detail`:可选展示信息。
|
|
@@ -226,7 +226,7 @@ Agent 调用 DSH 的 `ask_user_question` 工具时,插件通过 API Gateway
|
|
|
226
226
|
}
|
|
227
227
|
```
|
|
228
228
|
|
|
229
|
-
|
|
229
|
+
提交规则由移动网关在进入 Host waterfall 前严格校验:
|
|
230
230
|
|
|
231
231
|
- 必须一次提交这一批中的全部问题,`answers` 数量、顺序和 `id` 必须与 `questions` 一致。
|
|
232
232
|
- `selected` 中的值必须与原始 `options[].label` 完全一致,且不能重复。
|
|
@@ -264,13 +264,13 @@ Agent 调用 DSH 的 `ask_user_question` 工具时,插件通过 API Gateway
|
|
|
264
264
|
"outcome":"answered" }
|
|
265
265
|
```
|
|
266
266
|
|
|
267
|
-
`outcome` 为 `answered` 或 `cancelled
|
|
267
|
+
`outcome` 为 `answered` 或 `cancelled`。存在匹配 Session 的移动连接时,移动网关优先认领请求,多台移动设备中的第一个合法响应获胜;若没有匹配连接或连接全部断开,则通过 `next()` 回退给 WebUI/后续 Host answerer。所有移动连接都会收到由移动端完成的最终状态并应关闭对应选择界面。重连时网关会重放仍由它持有的问题;DSH 进程重启则会取消这些仅存在于运行时的问题。
|
|
268
268
|
|
|
269
269
|
---
|
|
270
270
|
|
|
271
271
|
### 3.2 操作审批
|
|
272
272
|
|
|
273
|
-
当 DSH
|
|
273
|
+
当 DSH 的工具管线要求人工授权时,插件会接入一次 Host `approval/request` waterfall,并向移动端投影为 `approval-requested`。这正是 Web UI 中“等待审批”卡片对应的能力:`reason` 是面向用户的审批说明,`toolName` 标识请求操作的工具,`callId` 可用于与实时工具调用轨迹关联。
|
|
274
274
|
|
|
275
275
|
#### `approval-requested` — 服务端推送待审批操作
|
|
276
276
|
|
|
@@ -288,7 +288,7 @@ Agent 调用 DSH 的 `ask_user_question` 工具时,插件通过 API Gateway
|
|
|
288
288
|
```
|
|
289
289
|
|
|
290
290
|
- `rpcId`:本次可回答请求的稳定 RPC ID;提交决定时必须原样返回。
|
|
291
|
-
- `approvalId
|
|
291
|
+
- `approvalId`:移动网关生成的本次审批关联 ID;同样必须原样返回,并用于将最终状态关联到本地审批卡片。
|
|
292
292
|
- `toolName`:请求审批的工具名。
|
|
293
293
|
- `callId` / `reason`:可选。前者可关联工具调用,后者应直接显示为待审批原因。
|
|
294
294
|
- `replay: true`:表示当前仍未决定的审批在移动端连接或切换 Session 后重放。客户端应按 `rpcId` 去重。
|
|
@@ -321,7 +321,7 @@ Agent 调用 DSH 的 `ask_user_question` 工具时,插件通过 API Gateway
|
|
|
321
321
|
"approvalId":"approval-1", "outcome":"allowed-once", "accepted":true }
|
|
322
322
|
```
|
|
323
323
|
|
|
324
|
-
|
|
324
|
+
如果另一台移动设备已经先作出决定,或请求已经回退给 WebUI/后续 answerer,则回执为 `accepted:false`,并附带 `reason:"not-pending"`。收到错误帧或未被接受的回执时,客户端应保留当前状态,等待最终状态或重新打开事件流。
|
|
325
325
|
|
|
326
326
|
#### `approval-resolved` — 服务端广播最终状态
|
|
327
327
|
|
|
@@ -330,7 +330,7 @@ Agent 调用 DSH 的 `ask_user_question` 工具时,插件通过 API Gateway
|
|
|
330
330
|
"approvalId":"approval-1", "outcome":"allowed-once" }
|
|
331
331
|
```
|
|
332
332
|
|
|
333
|
-
`outcome` 为 `allowed-once`、`rejected`、`cancelled` 或 `unavailable
|
|
333
|
+
`outcome` 为 `allowed-once`、`rejected`、`cancelled` 或 `unavailable`。所有移动连接都会收到由移动网关完成的最终状态并关闭对应审批卡片。移动端断线重连后,网关会重放仍由它持有的审批;已决或已回退的审批不会重放。
|
|
334
334
|
|
|
335
335
|
---
|
|
336
336
|
|
|
@@ -546,6 +546,8 @@ let image = [
|
|
|
546
546
|
| `search` | `query` | 会话全文搜索 |
|
|
547
547
|
| `session-stats` | `sessionId` | 执行统计投影(输入框统计条数据源) |
|
|
548
548
|
| `context-usage` | `sessionId` | token 用量 + 上下文占用投影 |
|
|
549
|
+
| `tasks` | `sessionId` | 当前任务列表(`todos` projection) |
|
|
550
|
+
| `goal` | `sessionId` | 当前目标及其 CAS 版本(`goal` projection) |
|
|
549
551
|
|
|
550
552
|
### `history` 详细
|
|
551
553
|
```json
|
|
@@ -659,7 +661,93 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
659
661
|
|
|
660
662
|
---
|
|
661
663
|
|
|
662
|
-
## 6.
|
|
664
|
+
## 6. 任务列表与目标
|
|
665
|
+
|
|
666
|
+
WebUI 中的“任务”与“进行中的目标”分别对应 DSH 的 `todos` 和 `goal` session projection。移动端进入会话后应请求 `tasks` 与 `goal` 取得基线;随后以 `tasks-updated` / `goal-updated` 实时更新 UI。
|
|
667
|
+
|
|
668
|
+
### 任务列表
|
|
669
|
+
|
|
670
|
+
```json
|
|
671
|
+
{ "type": "tasks", "sessionId": "session-abc" }
|
|
672
|
+
→ {
|
|
673
|
+
"kind": "tasks",
|
|
674
|
+
"sessionId": "session-abc",
|
|
675
|
+
"asOfSeq": 42,
|
|
676
|
+
"todos": [
|
|
677
|
+
{ "content": "检查 Android SDK", "status": "completed" },
|
|
678
|
+
{ "content": "创建项目", "status": "in_progress" },
|
|
679
|
+
{ "content": "构建 APK", "status": "pending" }
|
|
680
|
+
]
|
|
681
|
+
}
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
- `todos: null` 表示该会话尚未写入过任务列表;客户端可隐藏任务卡片。
|
|
685
|
+
- 任务由 Agent 的 `todo_write` 更新;移动端只读展示,不能直接改写。
|
|
686
|
+
|
|
687
|
+
### 当前目标
|
|
688
|
+
|
|
689
|
+
```json
|
|
690
|
+
{ "type": "goal", "sessionId": "session-abc" }
|
|
691
|
+
→ {
|
|
692
|
+
"kind": "goal",
|
|
693
|
+
"sessionId": "session-abc",
|
|
694
|
+
"asOfSeq": 42,
|
|
695
|
+
"goal": {
|
|
696
|
+
"goal": {
|
|
697
|
+
"id": "goal-opaque-id",
|
|
698
|
+
"revision": 7,
|
|
699
|
+
"objective": "初始化一个 Android app",
|
|
700
|
+
"phase": "active",
|
|
701
|
+
"maxGoalRounds": 12
|
|
702
|
+
},
|
|
703
|
+
"roundsStarted": 3,
|
|
704
|
+
"createdAt": 1787111700000,
|
|
705
|
+
"updatedAt": 1787111800000
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
`goal: null` 表示没有当前目标。所有目标写操作必须携带刚读取到的 `{id, revision}`。这是 DSH 的 compare-and-set 保护:当 WebUI 或另一台设备已经修改目标时,宿主拒绝陈旧 revision,移动端应重新请求 `goal` 后再提示用户重试。
|
|
711
|
+
|
|
712
|
+
| type | 参数 | 说明 |
|
|
713
|
+
|---|---|---|
|
|
714
|
+
| `goal-edit` | `sessionId`, `ref`, `objective?`, `maxGoalRounds?` | 修改目标名称(`objective`)或轮数上限,至少提供一项 |
|
|
715
|
+
| `goal-pause` | `sessionId`, `ref` | 暂停当前目标 |
|
|
716
|
+
| `goal-resume` | `sessionId`, `ref` | 继续已暂停/阻塞的目标 |
|
|
717
|
+
| `goal-clear` | `sessionId`, `ref` | 删除当前目标 |
|
|
718
|
+
|
|
719
|
+
更改目标名称:
|
|
720
|
+
|
|
721
|
+
```json
|
|
722
|
+
{ "type": "goal-edit", "sessionId": "session-abc",
|
|
723
|
+
"ref": { "id": "goal-opaque-id", "revision": 7 },
|
|
724
|
+
"objective": "完成 Android app 初始化" }
|
|
725
|
+
→ { "kind": "goal-edit", "sessionId": "session-abc",
|
|
726
|
+
"ref": { "id": "goal-opaque-id", "revision": 8 } }
|
|
727
|
+
```
|
|
728
|
+
|
|
729
|
+
暂停、继续和删除只替换 `type`:
|
|
730
|
+
|
|
731
|
+
```json
|
|
732
|
+
{ "type": "goal-pause", "sessionId": "session-abc", "ref": { "id": "goal-opaque-id", "revision": 8 } }
|
|
733
|
+
→ { "kind": "goal-pause", "sessionId": "session-abc", "ref": { "id": "goal-opaque-id", "revision": 9 } }
|
|
734
|
+
|
|
735
|
+
{ "type": "goal-clear", "sessionId": "session-abc", "ref": { "id": "goal-opaque-id", "revision": 9 } }
|
|
736
|
+
→ { "kind": "goal-clear", "sessionId": "session-abc", "cleared": true }
|
|
737
|
+
```
|
|
738
|
+
|
|
739
|
+
### 实时更新
|
|
740
|
+
|
|
741
|
+
```json
|
|
742
|
+
{ "kind": "tasks-updated", "sessionId": "session-abc", "asOfSeq": 43, "todos": [ ... ] }
|
|
743
|
+
{ "kind": "goal-updated", "sessionId": "session-abc", "asOfSeq": 44, "goal": { ... } }
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
服务端只转发 `todos` 与 `goal` projection;客户端按 `asOfSeq` 做高序号覆盖,避免较早推送回写较新的查询结果。
|
|
747
|
+
|
|
748
|
+
---
|
|
749
|
+
|
|
750
|
+
## 7. 工作区与目录
|
|
663
751
|
|
|
664
752
|
| type | 参数 | 说明 |
|
|
665
753
|
|---|---|---|
|
|
@@ -689,7 +777,7 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
689
777
|
|
|
690
778
|
---
|
|
691
779
|
|
|
692
|
-
##
|
|
780
|
+
## 8. 模型与思考等级
|
|
693
781
|
|
|
694
782
|
| type | 参数 | 说明 |
|
|
695
783
|
|---|---|---|
|
|
@@ -721,7 +809,7 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
721
809
|
|
|
722
810
|
---
|
|
723
811
|
|
|
724
|
-
##
|
|
812
|
+
## 9. 权限控制
|
|
725
813
|
|
|
726
814
|
| type | 参数 | 说明 |
|
|
727
815
|
|---|---|---|
|
|
@@ -736,7 +824,7 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
736
824
|
|
|
737
825
|
---
|
|
738
826
|
|
|
739
|
-
##
|
|
827
|
+
## 10. 新会话默认配置
|
|
740
828
|
|
|
741
829
|
| type | 参数 | 说明 |
|
|
742
830
|
|---|---|---|
|
|
@@ -754,7 +842,7 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
754
842
|
|
|
755
843
|
---
|
|
756
844
|
|
|
757
|
-
##
|
|
845
|
+
## 11. 分支(fork)
|
|
758
846
|
|
|
759
847
|
```json
|
|
760
848
|
{ "type": "fork", "sessionId": "session-abc", "atSeq": 42 }
|
|
@@ -765,7 +853,7 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
765
853
|
|
|
766
854
|
---
|
|
767
855
|
|
|
768
|
-
##
|
|
856
|
+
## 12. 宿主信息
|
|
769
857
|
|
|
770
858
|
| type | 返回 |
|
|
771
859
|
|---|---|
|
|
@@ -779,13 +867,14 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
779
867
|
|
|
780
868
|
---
|
|
781
869
|
|
|
782
|
-
##
|
|
870
|
+
## 13. 服务端主动推送
|
|
783
871
|
|
|
784
872
|
| kind | 触发时机 |
|
|
785
873
|
|---|---|
|
|
786
874
|
| `paired` | 首次配对成功;仅此一次返回长期设备 token |
|
|
787
|
-
| `hello` | 连接成功:`{ "kind":"hello", "protocol":3, "capabilities":["images","file-downloads"], "authenticated":true, "port":3080, "clients":1 }` |
|
|
875
|
+
| `hello` | 连接成功:`{ "kind":"hello", "protocol":3, "capabilities":["images","commands","tasks","goals","file-downloads"], "authenticated":true, "port":3080, "clients":1 }` |
|
|
788
876
|
| `event` | 任意会话的 agent 输出(见下) |
|
|
877
|
+
| `tasks-updated` / `goal-updated` | 当前会话的任务列表或目标 projection 发生变化 |
|
|
789
878
|
| `question-requested` / `question-resolved` | Human-in-the-loop 问题请求与最终状态 |
|
|
790
879
|
| `approval-requested` / `approval-resolved` | Human-in-the-loop 操作审批请求与最终状态 |
|
|
791
880
|
| `pong` / `subscribed` / `sent` | 对应请求的回复 |
|
|
@@ -805,7 +894,7 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
805
894
|
|
|
806
895
|
---
|
|
807
896
|
|
|
808
|
-
##
|
|
897
|
+
## 14. 端到端示例(Postman)
|
|
809
898
|
|
|
810
899
|
1. Connect → 收到 `hello`
|
|
811
900
|
2. `{"type":"sessions"}` → 挑 `sessionId`(或直接下一步自动建)
|
|
@@ -818,7 +907,7 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
818
907
|
|
|
819
908
|
---
|
|
820
909
|
|
|
821
|
-
##
|
|
910
|
+
## 15. 安全注意
|
|
822
911
|
|
|
823
912
|
- `/ws/mobile` 的移动网关默认关闭;本机 WebUI 手动开启后,若 5 分钟内没有设备成功连接会自动关闭
|
|
824
913
|
- 网关开启后仍要求已配对设备凭证;不要把 `requireAuth` 设为 `false` 后暴露到网络
|
|
@@ -833,7 +922,7 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
833
922
|
|
|
834
923
|
---
|
|
835
924
|
|
|
836
|
-
##
|
|
925
|
+
## 16. 版本历史(插件)
|
|
837
926
|
|
|
838
927
|
| 版本 | 新增 |
|
|
839
928
|
|---|---|
|
|
@@ -859,6 +948,8 @@ iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建
|
|
|
859
948
|
| v0.6.7 | 订阅已有 Session 时重放待处理 Human-in-the-loop 请求,并增加 Approval 端到端诊断日志与安装版本标记 |
|
|
860
949
|
| v0.6.8 | 会话工作目录受限的文件列表与分块下载:支持图片、文档、IPA、APK 等普通文件,含连接归属、路径越界防护、取消、超时和 SHA-256 完整性校验 |
|
|
861
950
|
| v0.6.9 | 服务端驱动的命令与技能目录:支持本地化 Hint、通用二级选项、专用命令执行,以及 command/compaction 生命周期事件;Host 命令不再作为用户 Prompt 发送 |
|
|
951
|
+
| v0.7.0 | 任务与 Goal 对齐:任务/Goal 基线查询、`todos`/`goal` 实时投影、Goal 改名、暂停、继续与删除 |
|
|
952
|
+
| v0.7.1 | DSH v0.1.2-rc.1 兼容:内部迁移至 Remote Gateway 与 Host waterfall,移除 APIProxy 依赖;`dsh-mobile-v1` 保持不变 |
|
|
862
953
|
|
|
863
954
|
---
|
|
864
955
|
|
package/README.md
CHANGED
|
@@ -4,7 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
# dsh-plugin-mobile-gateway
|
|
6
6
|
|
|
7
|
-
DeepSeek Harness
|
|
7
|
+
DeepSeek Harness 的设备鉴权移动网关,支持会话与实时事件、任务列表和当前 Goal 同步及管理、服务端驱动的命令和技能菜单、Human-in-the-loop、图片及文件传输。安装后,Harness WebUI 左侧边栏会出现“移动设备”入口,可直接开启网关、生成配对二维码和管理可信设备。
|
|
8
|
+
|
|
9
|
+
> v0.7.1:适配 DSH v0.1.2-rc.1 Remote Gateway,移除 APIProxy 依赖;移动端协议和现有功能保持兼容。
|
|
10
|
+
|
|
11
|
+
## 协议与 DSH 兼容层
|
|
12
|
+
|
|
13
|
+
移动端连接的是本项目维护的 `dsh-mobile-v1`,不是 DSH 的内部 Remote 协议。插件内部通过独立 Host Adapter 对接 DSH v0.1.2-rc.1 的 Remote Gateway;Session、Workspace、Settings、Commands、Goals 等 namespace 和严格参数只存在于该适配层。
|
|
14
|
+
|
|
15
|
+
因此本次从旧 APIProxy 迁移到 Remote Gateway 不要求现有移动端修改:WebSocket 子协议、`hello.protocol = 3`、配对鉴权和已有请求/响应字段保持不变。以后 DSH 调整 Remote API 时,优先只更新插件适配层及契约测试;只有移动协议本身发生不可兼容变化时,才会新增并并行支持新的移动端协议版本。
|
|
16
|
+
|
|
17
|
+
详细实施边界见 [Remote Gateway 重构实施计划](docs/remote-gateway-refactor-plan.md)。
|
|
8
18
|
|
|
9
19
|
- WebSocket:`/ws/mobile`
|
|
10
20
|
- 局域网:`ws://<局域网 IP>:3081/ws/mobile`
|
package/cordis.patch.yml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# dsh-plugin-mobile-gateway bundle patch.
|
|
2
2
|
# Applied after dsh-base and dsh-web-app: inserts the persistent /ws/mobile
|
|
3
|
-
# gateway row into the host plane (it consumes the
|
|
4
|
-
# and the
|
|
3
|
+
# gateway row into the host plane (it consumes `webServer`, the Host Remote
|
|
4
|
+
# Gateway and the `session/event` feed, and publishes no service of its own).
|
|
5
5
|
- insert:
|
|
6
6
|
- id: mobile-gateway
|
|
7
7
|
name: 'dsh-plugin-mobile-gateway'
|
|
@@ -379,7 +379,7 @@ DSH 用 Cordis 的依赖注入容器编排"服务行"。插件通过 `package.js
|
|
|
379
379
|
const plugin = {
|
|
380
380
|
name: 'mobile-gateway',
|
|
381
381
|
Config,
|
|
382
|
-
inject: ['webServer', '
|
|
382
|
+
inject: ['webServer', 'typertGateway', 'agentDefaultModel'],
|
|
383
383
|
apply(ctx, config) { /* ... */ },
|
|
384
384
|
}
|
|
385
385
|
```
|
|
@@ -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
|
+
|