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 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.6.9)。
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
- 二者都是 API Gateway 的临时请求,不属于持久化的 `session/event`,且都必须以其原始 `rpcId` 通过专用响应帧回答,不能作为普通 `message` 发送。
165
+ 二者都是 Host waterfall 的临时请求,不属于持久化的 `session/event`,且都必须以插件为该次请求生成的 `rpcId` 通过专用响应帧回答,不能作为普通 `message` 发送。该内部实现不改变移动端帧格式。
166
166
 
167
167
  ### 3.1 提问与回答
168
168
 
169
- Agent 调用 DSH 的 `ask_user_question` 工具时,插件通过 API Gateway 的 `events.mux()` 收到临时的待回答请求,并推送给移动端。
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`:API Gateway 为这一整批问题生成的稳定 ID。回答或取消时必须原样返回,客户端不得自行生成。
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
- 提交规则由 API Gateway 严格校验:
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`。WebUI、iOS 或其他客户端中的第一个合法响应获胜;所有移动连接都会收到最终状态并应关闭对应选择界面。移动端断线重连后,API Gateway 会重放仍待回答的问题;DSH 进程重启则会取消这些仅存在于运行时的问题。
267
+ `outcome` 为 `answered` 或 `cancelled`。存在匹配 Session 的移动连接时,移动网关优先认领请求,多台移动设备中的第一个合法响应获胜;若没有匹配连接或连接全部断开,则通过 `next()` 回退给 WebUI/后续 Host answerer。所有移动连接都会收到由移动端完成的最终状态并应关闭对应选择界面。重连时网关会重放仍由它持有的问题;DSH 进程重启则会取消这些仅存在于运行时的问题。
268
268
 
269
269
  ---
270
270
 
271
271
  ### 3.2 操作审批
272
272
 
273
- 当 DSH 的工具管线要求人工授权时,插件会从 API Gateway 收到一次 `approval/requested`。这正是 Web UI 中“等待审批”卡片对应的事件:`reason` 是面向用户的审批说明,`toolName` 标识请求操作的工具,`callId` 可用于与实时工具调用轨迹关联。
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`:审批审计 ID;同样必须原样返回,并用于将最终状态关联到本地审批卡片。
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
- 如果 Web UI 或另一台移动设备已经先作出决定,则回执为 `accepted:false`,并附带 `reason:"not-pending"`。收到错误帧或未被接受的回执时,客户端应保留当前状态,等待最终状态或重新打开事件流。
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`。所有移动连接都会收到最终状态并关闭对应审批卡片。移动端断线重连后,API Gateway 会重放仍待决定的审批;已决审批不会重放。
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
- ## 7. 模型与思考等级
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
- ## 8. 权限控制
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
- ## 9. 新会话默认配置
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
- ## 10. 分支(fork)
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
- ## 11. 宿主信息
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
- ## 12. 服务端主动推送
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
- ## 13. 端到端示例(Postman)
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
- ## 14. 安全注意
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
- ## 15. 版本历史(插件)
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 的设备鉴权移动网关,支持会话与实时事件、服务端驱动的命令和技能菜单、Human-in-the-loop、图片及文件传输。安装后,Harness WebUI 左侧边栏会出现“移动设备”入口,可直接开启网关、生成配对二维码和管理可信设备。
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 host `webServer` service
4
- # and the host `session/event` feed, and publishes no service of its own).
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', 'apiProxy', 'typertGateway', 'agentDefaultModel'],
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
+