dsh-plugin-mobile-gateway 0.7.7 → 0.9.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.
- package/PROTOCOL.md +68 -12
- package/README.md +32 -22
- package/cordis.patch.yml +4 -0
- package/docs/dsh-0.1.7-rc.1-compatibility-audit.md +52 -0
- package/docs/dsh-0.2.0-rc.2-plugin-management-mobile-integration.md +109 -0
- package/docs/multi-gateway-app-integration.md +2 -1
- package/lib/client.js +273 -18
- package/lib/cloudflare-tunnel.mjs +282 -0
- package/lib/cloudflared-binary.mjs +114 -0
- package/lib/dsh-host-adapter.mjs +47 -6
- package/lib/gateway-tools.mjs +36 -0
- package/lib/index.mjs +267 -47
- package/lib/plugin-management.mjs +194 -0
- package/lib/web-restart.mjs +47 -0
- 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`
|
|
3
|
+
移动端通过经过设备鉴权的 WebSocket 连接与 dsh 通信:订阅 agent 实时输出、发送文字和图片、处理 Human-in-the-loop 提问与操作审批、查询会话/工作区/历史、调整会话配置。本协议由持久化插件 `dsh-plugin-mobile-gateway` 实现。当前源码适配 DSH 0.2.0-rc.2 / Session format 4;移动端实时流接入见 [rc.2 接入说明](docs/dsh-rc2-mobile-integration.md),插件管理见 [RC2 插件管理对接方案](docs/dsh-0.2.0-rc.2-plugin-management-mobile-integration.md)。
|
|
4
4
|
|
|
5
5
|
- **本机端点**:`ws://127.0.0.1:3080/ws/mobile`(与 dsh web GUI 同端口)
|
|
6
6
|
- **局域网端点**:`ws://<电脑的私有局域网 IP>:3081/ws/mobile`(插件独立监听,只提供经过鉴权的 WebSocket)
|
|
@@ -100,7 +100,7 @@ const pairingText = Buffer.from(JSON.stringify(payload), 'utf8').toString('base6
|
|
|
100
100
|
| `gatewayName` | 同上 | 展示名称,允许改变,不能作为身份或鉴权依据 |
|
|
101
101
|
| `endpoints` | 配对载荷、`GET /mgw/status` | 规范化去重的 URL 字符串数组;不在 `hello` 或 `paired` 返回 |
|
|
102
102
|
|
|
103
|
-
配对仍保留 `publicUrl`,且它排在本次 `endpoints` 第一项。其后按本机配对请求 `endpoints`、插件配置 `endpoints
|
|
103
|
+
配对仍保留 `publicUrl`,且它排在本次 `endpoints` 第一项。其后按本机配对请求 `endpoints`、插件配置 `endpoints`、公网配置、已取得的 Cloudflare Tunnel 地址、已监听 LAN 地址的顺序合并。每个输入列表及合并结果最多 16 项,每项最多 2048 字符。允许 HTTP(S) 输入并转为 WS(S),拒绝凭证、query、fragment、未指定监听地址及公网明文 WS。
|
|
104
104
|
|
|
105
105
|
`POST /mgw/pair` 可额外传入 `endpoints: string[]`,只用于本次配对,不写入配置。移动端对新地址必须在发送凭证前建立信任;`hello` 的 ID 校验发生在鉴权后,不能替代 TLS 和地址确认。多个网关的 token 独立签发、保存和撤销;App 以网关连接上下文路由原有业务请求。
|
|
106
106
|
|
|
@@ -108,6 +108,10 @@ const pairingText = Buffer.from(JSON.stringify(payload), 'utf8').toString('base6
|
|
|
108
108
|
|
|
109
109
|
`GET /mgw/status` 增加身份、地址列表及 `gatewayMode`。模式的持久化不影响配对码有效期和文件传输超时。自动超时关闭若遇到磁盘写入失败,会保持当前进程关闭并记录错误;重启可能仍按旧保存模式运行,需修复存储后再次保存选择。
|
|
110
110
|
|
|
111
|
+
Windows/macOS PC 可从本机管理接口 `POST /mgw/cloudflare` 一键启用 Quick Tunnel:`{"enabled":true,"mode":"quick"}`,无需账户、域名或 Token;返回的随机公网地址在 `GET /mgw/status` 的 `cloudflare.publicUrl` 中,隧道重启后可能变化,App 需重新扫码确认。固定地址可用命名 Tunnel:`{"enabled":true,"mode":"named","hostname":"gateway.example.com","token":"<Tunnel Token>"}`;后续更新可省略 Token 或传空字符串以保留已保存的 Token,关闭时只需 `{"enabled":false}`。状态字段返回模式、支持状态、公开地址、本机入口端口、连接状态和错误,但绝不返回 Token。启用时网关自动切为常驻模式;配置与 Token 保存在独立的 `0600` 文件中。插件会在需要时下载并校验 Cloudflare 官方 `cloudflared` 二进制。两种模式仅在 `127.0.0.1:3082`(可配置)接受已鉴权的 `/ws/mobile` WebSocket,其他 HTTP 路径返回 404;网关关闭或插件卸载时该入口及受管进程一同停止。
|
|
112
|
+
|
|
113
|
+
`POST /mgw/cloudflare/restart` 无需请求体,仅在网关与 Tunnel 均已开启时重启隧道;未开启时返回 `409`。当前移动连接会断开,Quick Tunnel 的地址可能变化,客户端需重新扫码确认。此操作不会重启 DSH Web,也不会将 `cloudflared` 自动更新到最新版本。
|
|
114
|
+
|
|
111
115
|
完整配对示例、旧 App 迁移、地址信任与错误处理见 [App 对接说明](docs/multi-gateway-app-integration.md)。
|
|
112
116
|
|
|
113
117
|
#### iOS 对接示例
|
|
@@ -319,6 +323,7 @@ waterfall,并向移动端投影为 `approval-requested`,同时保留 WebUI a
|
|
|
319
323
|
"toolName": "bash",
|
|
320
324
|
"callId": "call-42",
|
|
321
325
|
"reason": "escalate sandbox to danger-full-access",
|
|
326
|
+
"displayReason": { "en": "Allow this command?", "zh-CN": "允许执行此命令?" },
|
|
322
327
|
"replay": true
|
|
323
328
|
}
|
|
324
329
|
```
|
|
@@ -326,7 +331,8 @@ waterfall,并向移动端投影为 `approval-requested`,同时保留 WebUI a
|
|
|
326
331
|
- `rpcId`:本次可回答请求的稳定 RPC ID;提交决定时必须原样返回。
|
|
327
332
|
- `approvalId`:移动网关生成的本次审批关联 ID;同样必须原样返回,并用于将最终状态关联到本地审批卡片。
|
|
328
333
|
- `toolName`:请求审批的工具名。
|
|
329
|
-
- `callId` / `reason
|
|
334
|
+
- `callId` / `reason`:可选。前者可关联工具调用,后者是原始审批说明。
|
|
335
|
+
- `displayReason`:rc.2 新增的可选多语言说明;客户端优先按当前语言显示,缺失时回退到 `reason`。该字段也会随待处理请求重放。
|
|
330
336
|
- `replay: true`:表示当前仍未决定的审批在移动端连接或切换 Session 后重放。客户端应按 `rpcId` 去重。
|
|
331
337
|
|
|
332
338
|
审批请求不含工具完整参数;移动端应将 `reason` 与可见的工具调用轨迹作为展示依据,不应自行推断或构造命令。
|
|
@@ -568,7 +574,7 @@ let image = [
|
|
|
568
574
|
|
|
569
575
|
### 排队消息同步与修改
|
|
570
576
|
|
|
571
|
-
`hello.capabilities` 包含 `queue-control` 时,控制连接会收到 Host
|
|
577
|
+
`hello.capabilities` 包含 `queue-control` 时,控制连接会收到 Host 当前待处理消息。DSH 0.1.7-rc.1 从 `session/control` 的 `projections.values.inbox` 提供待处理消息:`next-turn` 映射为 `queued`,`next-step` 按来源映射为 `steering` 或 `context`。连接初始化或 Host 控制流重连后,网关发送完整快照:
|
|
572
578
|
|
|
573
579
|
```json
|
|
574
580
|
{
|
|
@@ -589,7 +595,7 @@ let image = [
|
|
|
589
595
|
}
|
|
590
596
|
```
|
|
591
597
|
|
|
592
|
-
之后某个 Session
|
|
598
|
+
之后某个 Session 的 `inbox` 投影发生变化时,网关发送该 Session 的完整替换值:
|
|
593
599
|
|
|
594
600
|
```json
|
|
595
601
|
{ "kind": "session-queue", "sessionId": "session-abc", "items": [] }
|
|
@@ -696,8 +702,8 @@ WebUI、App 或其他客户端造成的变化通过以下帧主动推送:
|
|
|
696
702
|
- 返回**原始 SessionEvent**(`{type, seq, time, data}`,方案A),可选裁剪
|
|
697
703
|
- 图片不会内联进历史页。`user/message.data.content[]` 中的图片块为 `{ "type":"image", "attachment": ImageAttachmentRef }`;iOS 使用其中的 `attachmentId` 请求图片数据
|
|
698
704
|
- `maxBytes`:单帧字节预算,默认 **4 MiB**;超预算保留最新部分并给出 `nextBeforeSeq` 续页(客户端 16 MiB 上限的安全余量)
|
|
699
|
-
- `view: "conversation"`:**对话裁剪模式**——隐藏系统消息与 request/header、request/context,移除 Assistant 事件的内嵌 `data.stream`,`tool/result`
|
|
700
|
-
- 分页:`hasMore` 为真时用 `beforeSeq: nextBeforeSeq, historyFormatVersion:
|
|
705
|
+
- `view: "conversation"`:**对话裁剪模式**——隐藏系统消息与 request/header、request/context,移除 Assistant 事件的内嵌 `data.stream`,`tool/result` 文本截断到 2000 字符
|
|
706
|
+
- 分页:`hasMore` 为真时用 `beforeSeq: nextBeforeSeq, historyFormatVersion: 4` 请求更早一页
|
|
701
707
|
|
|
702
708
|
```json
|
|
703
709
|
→ { "kind": "history", "sessionId": "session-abc", "events": [ ...原始事件... ],
|
|
@@ -954,7 +960,7 @@ WebUI 中的“任务”与“进行中的目标”分别对应 DSH 的 `todos`
|
|
|
954
960
|
|
|
955
961
|
| type | 参数 | 说明 |
|
|
956
962
|
|---|---|---|
|
|
957
|
-
| `permission-options` | `sessionId?` |
|
|
963
|
+
| `permission-options` | `sessionId?` | Host `permissionPresets/catalog` 的 `options`、`defaultOptions`、`defaultPreset`,以及该会话当前生效值(`sessionPermissions`) |
|
|
958
964
|
| `permission` | `sessionId`, `name` | 切换**该会话**的权限 preset(走官方 `/permission` 命令,不触发模型) |
|
|
959
965
|
|
|
960
966
|
```json
|
|
@@ -972,17 +978,19 @@ WebUI 中的“任务”与“进行中的目标”分别对应 DSH 的 `todos`
|
|
|
972
978
|
| `agent-presets` | — | preset 名册(含 `isDefault` 标记) |
|
|
973
979
|
| `session-agent-preset` | `sessionId`, `requestId?` | 读取当前会话的 `agentPreset` 和 `locked` |
|
|
974
980
|
| `select-agent-preset` | `sessionId`, `agentPreset`, `requestId?` | 在首次对话前修改当前会话模式,保持 session ID |
|
|
975
|
-
| `defaults` | — | 读取默认
|
|
981
|
+
| `defaults` | — | 读取默认 Agent 预设、默认权限及动态可选默认权限(`permissionDefaultOptions`) |
|
|
976
982
|
| `set-default` | `target`(agent-preset\|permission), `value` | 修改默认预设/默认权限(全局) |
|
|
977
983
|
|
|
978
984
|
```json
|
|
979
985
|
{ "type": "defaults" }
|
|
980
|
-
→ { "kind": "defaults", "agentPresetDefault": "standard", "permissionDefault": "ask" }
|
|
986
|
+
→ { "kind": "defaults", "agentPresetDefault": "standard", "permissionDefault": "ask", "permissionDefaultOptions": [{ "value": "ask", "name": "Ask" }], "modeSelectionEnabled": true }
|
|
981
987
|
|
|
982
988
|
{ "type": "set-default", "target": "agent-preset", "value": "minimal" }
|
|
983
989
|
→ { "kind": "set-default", "target": "agent-preset", "value": "minimal", "applied": true }
|
|
984
990
|
```
|
|
985
991
|
|
|
992
|
+
DSH 0.1.7-rc.2 的 Agent preset 名册不再包含 `modeSelectionEnabled`。网关在字段缺失时仍允许移动端选择和保存默认模式;旧版 Host 明确返回 `false` 时沿用其禁用语义。这个移动端字段不代表桌面端「代码工作工具」的本地开关状态。
|
|
993
|
+
|
|
986
994
|
### 当前会话模式选择
|
|
987
995
|
|
|
988
996
|
`hello.capabilities` 包含 `session-agent-preset` 时,可以进入已创建的空白会话后选模式。
|
|
@@ -1029,7 +1037,7 @@ Control/旧单连接接收全局模式状态增量,不受所订阅会话过滤
|
|
|
1029
1037
|
## 11. 分支(fork)
|
|
1030
1038
|
|
|
1031
1039
|
```json
|
|
1032
|
-
{ "type": "fork", "sessionId": "session-abc", "atSeq": 42, "historyFormatVersion":
|
|
1040
|
+
{ "type": "fork", "sessionId": "session-abc", "atSeq": 42, "historyFormatVersion": 4 }
|
|
1033
1041
|
→ { "kind": "fork", "sessionId": "session-分支新会话" }
|
|
1034
1042
|
```
|
|
1035
1043
|
- `atSeq`:从该消息所在的**完整一轮**分叉(省略 = 最近完成的 turn);进行中的 turn 分叉会报 `fork-unavailable`
|
|
@@ -1062,6 +1070,7 @@ Control/旧单连接接收全局模式状态增量,不受所订阅会话过滤
|
|
|
1062
1070
|
| `session-archives` | Host 的完整 Session 归档集合在连接初始化或 WebUI/App 归档后变化 |
|
|
1063
1071
|
| `session-title-changed` | 任意客户端写入新的持久化 Session 名称 |
|
|
1064
1072
|
| `tasks-updated` / `goal-updated` | 当前会话的任务列表或目标 projection 发生变化 |
|
|
1073
|
+
| `schedule-changed` | Host 定时任务目录已变化;无载荷,重新请求 `schedule-catalog` 或当前 Session 的 `schedule-list` |
|
|
1065
1074
|
| `question-requested` / `question-resolved` | Human-in-the-loop 问题请求与最终状态 |
|
|
1066
1075
|
| `approval-requested` / `approval-resolved` | Human-in-the-loop 操作审批请求与最终状态 |
|
|
1067
1076
|
| `pong` / `subscribed` / `sent` | 对应请求的回复 |
|
|
@@ -1081,6 +1090,12 @@ Control/旧单连接接收全局模式状态增量,不受所订阅会话过滤
|
|
|
1081
1090
|
- `tool/result` → `{turn, step, callId, isError, preview(≤400字符)}`
|
|
1082
1091
|
- `turn/start|end` / `step/start|end` → `{turn, step, reason?}`
|
|
1083
1092
|
|
|
1093
|
+
### 运行中计时提示
|
|
1094
|
+
|
|
1095
|
+
DSH Web 的“深度求索中,用时 22 秒 ···”是客户端文案,不作为独立 Host 事件或网关帧发送。移动端可用 `sessions.items[].running` 判断会话是否运行;订阅中的 `event` 帧在根节点带 `time`,`event.type: turn/start` 标记本轮开始,`turn/end` 标记结束。收到 `turn/start` 后以 `Date.now() - time` 在本地更新秒数,收到 `turn/end` 或会话 `running: false` 后停止。计时文本、单位和动画由移动端本地化渲染。
|
|
1096
|
+
|
|
1097
|
+
重连得到的 `session-snapshot.events` 只含最近窗口,长时间运行的 Turn 可能已把 `turn/start` 挤出窗口。此时 `sessions.items[].running` 仍可用来显示“深度求索中”,但不能只凭快照推断准确的起始时间;若需要恢复精确计时,应沿 `nextBeforeSeq` 读取更早历史,找到最近尚未结束的 `turn/start`。未取得起始时间前不要从重连时刻重新计算并声称是整轮耗时。
|
|
1098
|
+
|
|
1084
1099
|
---
|
|
1085
1100
|
|
|
1086
1101
|
## 14. 端到端示例(Postman)
|
|
@@ -1090,7 +1105,7 @@ Control/旧单连接接收全局模式状态增量,不受所订阅会话过滤
|
|
|
1090
1105
|
3. `{"type":"subscribe","sessionId":"session-abc","assistantStream":true}`,等待 `session-snapshot`
|
|
1091
1106
|
4. `{"type":"message","sessionId":"session-abc","text":"帮我查一下deepseek"}` → `sent`
|
|
1092
1107
|
5. 盯着 Messages 面板:独立 `assistant-stream` 展示 token,`event` 流提供已提交的消息和工具事件
|
|
1093
|
-
6. `{"type":"history","sessionId":"session-abc","view":"conversation","maxMessages":60}` → 最近历史(自动分页用 `beforeSeq: nextBeforeSeq, historyFormatVersion:
|
|
1108
|
+
6. `{"type":"history","sessionId":"session-abc","view":"conversation","maxMessages":60}` → 最近历史(自动分页用 `beforeSeq: nextBeforeSeq, historyFormatVersion: 4`)
|
|
1094
1109
|
7. `{"type":"session-stats","sessionId":"session-abc"}` → 统计条数据
|
|
1095
1110
|
8. 完事 `{"type":"unsubscribe"}` 或 Disconnect
|
|
1096
1111
|
|
|
@@ -1115,6 +1130,7 @@ Control/旧单连接接收全局模式状态增量,不受所订阅会话过滤
|
|
|
1115
1130
|
|
|
1116
1131
|
| 版本 | 新增 |
|
|
1117
1132
|
|---|---|
|
|
1133
|
+
| v0.9.0(当前源码) | DSH 0.2.0-rc.2 插件管理;PC 端 Cloudflare Quick / 命名 Tunnel 及独立重启;插件私有 `cloudflared` 缓存及校验;配对入口选择指引;网关重启与 DSH Web 停止工具 |
|
|
1118
1134
|
| v0.7.2 | 独立对话/控制连接;空 Session 创建;停止生成与稍后继续;排队消息同步及编辑/删除/Steer;App 归档/重命名 Session;WebUI 归档集合和名称变化实时同步到 App |
|
|
1119
1135
|
| v0.1.5 | workspace-create / directories / host |
|
|
1120
1136
|
| v0.1.6 | 修复消息分发器遗漏(host/directories/workspace-create 未路由) |
|
|
@@ -1143,6 +1159,46 @@ Control/旧单连接接收全局模式状态增量,不受所订阅会话过滤
|
|
|
1143
1159
|
|
|
1144
1160
|
---
|
|
1145
1161
|
|
|
1162
|
+
## 17. 定时任务(DSH 0.1.7-rc.2)
|
|
1163
|
+
|
|
1164
|
+
以下请求走控制连接,网关映射到 Host `schedule` Remote namespace。Host 未启用定时任务时返回统一的 `kind: error`,`requestType` 保留原请求类型。定时任务与第 6 节的 Session `todos` 是不同的数据源。
|
|
1165
|
+
|
|
1166
|
+
| 请求 type | 参数 | 成功响应 |
|
|
1167
|
+
|---|---|---|
|
|
1168
|
+
| `schedule-catalog` | — | `kind: schedule-catalog`, `items`: 全宿主活动与已结束提醒,包含原 `sessionId`、`status` 和可选最近投递回执 |
|
|
1169
|
+
| `schedule-list` | `sessionId` | `kind: schedule-list`, `sessionId`, `items`: 当前会话活动提醒 |
|
|
1170
|
+
| `schedule-history` | `sessionId`, `id`, `limit` 1–100, `before?` | `kind: schedule-history`, `records`(从新到旧)、`retention`、`nextBefore?`;任务或游标未找到时在成功帧中返回 `code` |
|
|
1171
|
+
| `schedule-update` | `sessionId`, `id`, 完整的 `expected`, `title?`, `prompt?`, `change?`, `requestId?` | `kind: schedule-update`, `updated`, `record`;并发冲突、已结束或未找到时返回 `updated:false` 与 `code` |
|
|
1172
|
+
| `schedule-delete` | `sessionId`, `id`, `requestId?` | `kind: schedule-delete`, `deleted`;未找到时返回 `deleted:false` 与 `code` |
|
|
1173
|
+
|
|
1174
|
+
`expected` 必须是编辑前从 `schedule-list` 或 `schedule-catalog` 取得的完整任务记录,`id` 必须匹配;不要只传 id 或重建部分字段。网关会去掉目录行额外的 `sessionId`、`status`、`lastDelivery` 元数据后调用 Host。`change` 使用 Host 的 `at`、`every`、`daily`、`weekly`、`cron` 判别结构。更新与删除只对原会话绑定生效;`requestId` 若提供会原样回显。删除会同时移除该任务的投递记录,不撤回已入队的消息。
|
|
1175
|
+
|
|
1176
|
+
rc.2 没有 `schedule/create` Remote 方法。新提醒由 Agent 的 `schedule_create` 工具创建;到期提醒作为 `source.kind: schedule` 的消息进入原 Session。Host 的无载荷 `schedule/changed` 被投影为移动端 `schedule-changed`;实现定时任务管理页面的客户端应在收到通知、重连或完成更新/删除后重新查询目录。当前移动端只接入协议请求、解码与变更帧识别,尚未提供管理页面。`hello.capabilities` 中的 `schedule-management` 表示网关支持这些请求,实际使用仍取决于 Host 是否启用定时任务。
|
|
1177
|
+
|
|
1178
|
+
## 18. 插件管理(DSH 0.2.0-rc.2)
|
|
1179
|
+
|
|
1180
|
+
`hello.capabilities` 包含 `plugin-management-v1`。插件请求走控制连接,必须使用已配对设备 token;即使本机调试关闭了普通连接鉴权,匿名连接也不能执行这些请求。每个请求必须有非空 `requestId`,响应回显。`plugin-catalog` 的 `managementAvailable` 决定当前 Host profile 是否实际支持管理。
|
|
1181
|
+
|
|
1182
|
+
| 请求 type | 主要参数 | 成功响应 |
|
|
1183
|
+
|---|---|---|
|
|
1184
|
+
| `plugin-catalog` | `requestId` | 同名 kind;`managementAvailable`、`bundles`(每项含 `experimental`)、`plugins`、`officialItems`、`settingsWritable` |
|
|
1185
|
+
| `plugin-registries` | `requestId` | 同名 kind;`registry`、`fallbackRegistries`、`resolved` |
|
|
1186
|
+
| `plugin-inspect` | `requestId`, `spec`, `registry?` | `plugin-inspection`;`result` 为 accepted/refused 检查结果 |
|
|
1187
|
+
| `plugin-install` | `requestId`, `spec`, `enabled?`, `registry?`, `approvedBuilds?` | `plugin-install-result`;`result` 为 Host `ChangeResult` |
|
|
1188
|
+
| `plugin-install-status` / `plugin-install-cancel` | `requestId`, `installId` | 同名 kind;`result` 为安装结果/null 或取消状态 |
|
|
1189
|
+
| `plugin-bundle-set-enabled` | `requestId`, `name`, `enabled` | 同名 kind;`result` 为 `ChangeResult` |
|
|
1190
|
+
| `plugin-entry-set-enabled` | `requestId`, `entryId`, `enabled` | 同名 kind;`result` 为 `ChangeResult` |
|
|
1191
|
+
| `plugin-remove` | `requestId`, `name` | 同名 kind;`result` 为 `ChangeResult` |
|
|
1192
|
+
| `plugin-settings` | `requestId`, `ns?` | 同名 kind;全部 namespace 或指定的 `namespace` 视图 |
|
|
1193
|
+
| `plugin-settings-mutate` | `requestId`, `ns`, `expectedRevision`, `ops` | 同名 kind;返回修改后的 namespace 视图 |
|
|
1194
|
+
| `plugin-version-exemptions` / `plugin-version-exemption-set` | `requestId`;写入还需 `packageVersion`, `runtimeVersion`, `enabled`, `acceptRisk?` | 同名 kind;豁免列表或 `ChangeResult` |
|
|
1195
|
+
|
|
1196
|
+
Host 的 `plugin-manager/changed` 转为 `plugins-changed`(含 `reason`),`settings/document-updated` 转为 `plugin-settings-changed`(含 `ns`、`revision`)。`plugin-manager/install-state` 和 `plugin-manager/install-log` 只发给发起安装的连接,分别转为 `plugin-install-state` 和 `plugin-install-log`;它们的 `requestId` 就是原安装请求 ID。断线后用新的请求 ID 调 `plugin-install-status`,`installId` 填原安装 ID;切勿直接重复安装。关闭/卸载网关插件自身可能先断线,再由 DSH Web/CLI 恢复。
|
|
1197
|
+
|
|
1198
|
+
详细字段、官方/第三方页面分组、安装进度、配置 schema、错误处理和 DSH Mobile 验收步骤见 [插件管理对接方案](docs/dsh-0.2.0-rc.2-plugin-management-mobile-integration.md)。
|
|
1199
|
+
|
|
1200
|
+
---
|
|
1201
|
+
|
|
1146
1202
|
*协议与插件源码同源维护:`dsh-plugin-mobile-gateway/lib/index.mjs` 顶部注释即协议摘要。*
|
|
1147
1203
|
|
|
1148
1204
|
### 提前创建空会话
|
package/README.md
CHANGED
|
@@ -6,21 +6,23 @@
|
|
|
6
6
|
|
|
7
7
|
DeepSeek Harness 的设备鉴权移动网关,支持会话与实时事件、排队消息同步及编辑/删除/Steer、Session 归档和重命名的双向同步、停止当前生成并稍后继续、任务列表和当前 Goal 同步及管理、服务端驱动的命令和技能菜单、Human-in-the-loop、图片及文件传输。安装后,Harness WebUI 左侧边栏会出现“移动设备”入口,可直接开启网关、生成配对二维码和管理可信设备。
|
|
8
8
|
|
|
9
|
-
> 当前源码以 **DSH 0.
|
|
9
|
+
> 当前源码以 **DSH 0.2.0-rc.2** 为适配基线,使用 Session format 4;旧版历史游标需重新取得基线。
|
|
10
10
|
>
|
|
11
11
|
> 实时流已改为独立 `assistant-stream` 帧,移动端需要按 [rc.2 接入说明](docs/dsh-rc2-mobile-integration.md) 更新订阅、缓存与分页处理。当前修改尚未发布。
|
|
12
12
|
>
|
|
13
|
+
> v0.9.0(当前源码):新增 DSH 0.2.0-rc.2 插件管理能力;PC 端可在面板开启、重启 Cloudflare Quick / 命名 Tunnel,使用插件私有缓存中的 `cloudflared` 并校验文件;配对面板增加局域网与外网入口选择指引;工具菜单可重启网关或停止 DSH Web,重启时不再打开新标签页。
|
|
14
|
+
>
|
|
13
15
|
> v0.7.3:优化移动网关运行模式下拉框的箭头间距。
|
|
14
16
|
>
|
|
15
17
|
> v0.7.2:新增独立对话/控制连接、空 Session 创建、停止生成与稍后继续、排队消息同步及编辑/删除/Steer,以及 Session 归档和重命名的双向同步。
|
|
16
18
|
|
|
17
19
|
## 协议与 DSH 兼容层
|
|
18
20
|
|
|
19
|
-
移动端连接的是本项目维护的 `dsh-mobile-v1`,不是 DSH 的内部 Remote 协议。插件内部通过独立 Host Adapter 对接 DSH 0.
|
|
21
|
+
移动端连接的是本项目维护的 `dsh-mobile-v1`,不是 DSH 的内部 Remote 协议。插件内部通过独立 Host Adapter 对接 DSH 0.2.0-rc.2 的 Remote Gateway;Session、Workspace、Settings、Commands、Goals、Schedule、Permission Presets、Plugin Manager 等 namespace 和严格参数只存在于该适配层。插件管理的请求帧及 DSH Mobile 页面方案见 [RC2 插件管理对接方案](docs/dsh-0.2.0-rc.2-plugin-management-mobile-integration.md)。
|
|
20
22
|
|
|
21
23
|
配对鉴权和 `dsh-mobile-v1` / `hello.protocol = 3` 保持不变。新版实时 token 不占用持久事件的 `seq`:客户端显式订阅 `assistantStream: true`,接收原子的 `session-snapshot` 和独立增量;普通 `event` 只携带持久事件。未接入新订阅的客户端只能收到持久消息。
|
|
22
24
|
|
|
23
|
-
历史响应包含 `historyFormatVersion` 和 `cursor`。客户端格式变化时应清理历史缓存、重新安装基线;携带 `beforeSeq` 的分页或携带 `atSeq` 的 fork 必须同时发送 `historyFormatVersion:
|
|
25
|
+
历史响应包含 `historyFormatVersion` 和 `cursor`。客户端格式变化时应清理历史缓存、重新安装基线;携带 `beforeSeq` 的分页或携带 `atSeq` 的 fork 必须同时发送 `historyFormatVersion: 4`。
|
|
24
26
|
当前协议接入与验收见 [rc.2 移动端接入说明](docs/dsh-rc2-mobile-integration.md);早期迁移记录见 [Remote Gateway 重构实施计划](docs/remote-gateway-refactor-plan.md)。
|
|
25
27
|
|
|
26
28
|
- WebSocket:`/ws/mobile`
|
|
@@ -111,7 +113,7 @@ dsh web
|
|
|
111
113
|
1. 打开 WebUI 的“移动设备”。
|
|
112
114
|
2. 将“网关运行模式”设为“常驻开启”(短期配对也可选“临时开启”)。
|
|
113
115
|
3. 保持“设备鉴权”开启。
|
|
114
|
-
4.
|
|
116
|
+
4. 在“配对连接方式”选择“局域网直连”,确认 WebSocket 地址为 `ws://<电脑局域网 IP>:3081/ws/mobile`。即使 Cloudflare Tunnel 已开启,也无需关闭它。选择框旁的问号会按当前网关环境解释各入口并给出场景建议,不会自动更改选择。
|
|
115
117
|
5. 填写设备名称并点击“生成配对二维码”。
|
|
116
118
|
6. 在 iOS 客户端打开“设备认证”,扫描二维码。
|
|
117
119
|
7. WebUI 的可信设备显示“在线”后即完成。
|
|
@@ -183,9 +185,25 @@ http://127.0.0.1:<本地端口>
|
|
|
183
185
|
|
|
184
186
|
## Windows / macOS 家用电脑远程连接
|
|
185
187
|
|
|
186
|
-
家用电脑通常没有固定公网 IP
|
|
188
|
+
家用电脑通常没有固定公网 IP,不建议配置路由器端口转发。Windows / macOS 面板提供两种可选入口:无需账户或域名的 Quick Tunnel,以及使用固定域名的命名 Tunnel。插件会在首次开启时自动下载并校验 Cloudflare 官方 `cloudflared`,保存在当前用户的缓存目录;无需手动安装系统服务。
|
|
189
|
+
|
|
190
|
+
使用前先启动 `dsh web`。电脑必须保持开机、联网且 DSH 进程运行;休眠或进程退出后外网连接会中断。
|
|
191
|
+
|
|
192
|
+
### Cloudflare Quick Tunnel(一键临时接入)
|
|
193
|
+
|
|
194
|
+
在「移动设备 → Cloudflare Tunnel」选择「Quick Tunnel · 无需域名」,点击「一键开启」。面板会在首次运行时下载 `cloudflared`,连接后显示随机的 `wss://<名称>.trycloudflare.com/ws/mobile`,并自动将其填为配对地址。需要在局域网内直连时,将下方“配对连接方式”改为“局域网直连”;Tunnel 可以继续开启。生成二维码,用手机 App 扫码即可。关闭开关会停止隧道;DSH 再次启动时会恢复已开启的 Quick 模式并取得**新的随机地址**。
|
|
195
|
+
|
|
196
|
+
Quick Tunnel 无需 Cloudflare 账户、Token 或自己的公网域名,但地址在 DSH 重启或隧道重连后可能变化。手机 App 只信任已确认的地址,因此地址变化后需在面板重新生成二维码并扫码更新配对;**仅凭 PC 在线不能保证旧地址继续可用**。Cloudflare 将 Quick Tunnel 定位于测试和开发,当前有并发请求上限且不提供可用性保证。参见 [Cloudflare Quick Tunnel 文档](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/do-more-with-tunnels/trycloudflare/)。
|
|
197
|
+
|
|
198
|
+
面板中的“更新 Tunnel 配置”保存所选模式及命名 Tunnel 参数;配置不变时不会重新连接。“重启 Tunnel”会断开当前移动连接并重建 Cloudflare 隧道,不会重启 DSH Web;Quick Tunnel 重启后可能取得新地址,旧二维码会清除,需按新地址重新扫码。此操作不会自动升级 `cloudflared` 到最新版本。
|
|
199
|
+
|
|
200
|
+
### Cloudflare 命名 Tunnel(固定地址)
|
|
201
|
+
|
|
202
|
+
1. 在 [Cloudflare 控制台创建远程管理的 Tunnel](https://developers.cloudflare.com/tunnel/get-started/),添加一个公开域名(例如 `gateway.example.com`),将该路由的 **Service URL** 设为 `http://127.0.0.1:3082`。无需安装 Cloudflare 系统服务,也无需开放路由器入站端口。
|
|
203
|
+
2. 从 Tunnel 的「Add a replica」安装命令中复制 Token,在 DSH「移动设备 → Cloudflare Tunnel」选择「命名 Tunnel · 固定域名」,输入公开域名和 Token 后开启。插件会将网关设为常驻模式,启动本机专用入口和 `cloudflared`。面板显示「已连接 Cloudflare」后使用自动填入的 `wss://gateway.example.com/ws/mobile` 生成配对二维码。
|
|
204
|
+
3. 以后启动 `dsh web` 会自动恢复已开启的 Tunnel;在面板点击「关闭 Cloudflare Tunnel」会停止插件管理的 `cloudflared` 并关闭本机专用入口。若手动关闭网关,Tunnel 会暂停,重新开启网关后恢复。
|
|
187
205
|
|
|
188
|
-
|
|
206
|
+
两种 Cloudflare 入口都只接受 `/ws/mobile` WebSocket,其他 HTTP 路径(包括 DSH WebUI 和 `/mgw` 管理接口)返回 404;设备凭证始终必需,即使本机 Debug 鉴权被关闭也一样。命名 Tunnel 的 Token 保存在 `<deviceFile>.cloudflare.json`(默认 `~/.dsh/mobile-gateway-devices.json.cloudflare.json`),权限为 `0600`,不会返回给浏览器。默认使用 DSH 私有的 `cloudflared-bin` 缓存,不读取系统或其他 App 的 `cloudflared`;每次启动 Tunnel 都校验缓存文件,只有缓存缺失或损坏时才下载固定版本并核对 Cloudflare 官方发布资产的 SHA-256。仅在显式配置 `cloudflaredPath` 时使用外部程序。使用自定义 `cloudflarePort` 时,命名 Tunnel 路由的 Service URL 端口也要相应修改。
|
|
189
207
|
|
|
190
208
|
### Tailscale(推荐长期使用)
|
|
191
209
|
|
|
@@ -201,20 +219,6 @@ tailscale serve --bg 3081
|
|
|
201
219
|
|
|
202
220
|
Tailscale Serve 只允许同一 Tailnet 中符合访问规则的设备连接,并自动提供 HTTPS。可用 `tailscale serve reset` 停止转发。参见 [Tailscale Serve 文档](https://tailscale.com/docs/reference/tailscale-cli/serve)。
|
|
203
221
|
|
|
204
|
-
### Cloudflare Quick Tunnel(仅临时调试)
|
|
205
|
-
|
|
206
|
-
1. 安装 [cloudflared](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/downloads/)。
|
|
207
|
-
2. 在 Windows PowerShell 或 macOS 终端执行:
|
|
208
|
-
|
|
209
|
-
```bash
|
|
210
|
-
cloudflared tunnel --url http://127.0.0.1:3081
|
|
211
|
-
```
|
|
212
|
-
|
|
213
|
-
3. 命令行会显示随机的 `https://<随机名称>.trycloudflare.com` 地址。
|
|
214
|
-
4. 将地址改为 `wss://<随机名称>.trycloudflare.com/ws/mobile`,填入 WebUI 的“WebSocket 地址”,再生成二维码配对。
|
|
215
|
-
|
|
216
|
-
保持该命令运行;停止命令后隧道立即失效。Quick Tunnel 的地址每次可能变化,且没有可用性保证,不适合正式或长期使用。公网调试时必须保持“设备鉴权”开启。参见 [Cloudflare Quick Tunnel 文档](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/do-more-with-tunnels/trycloudflare/)。
|
|
217
|
-
|
|
218
222
|
## 网关配置方式总览
|
|
219
223
|
|
|
220
224
|
| 使用场景 | 推荐入口 | iOS WebSocket 地址 | 需要的额外配置 | 端口与鉴权 |
|
|
@@ -222,7 +226,9 @@ cloudflared tunnel --url http://127.0.0.1:3081
|
|
|
222
226
|
| 同一局域网 | 插件局域网入口 | `ws://<电脑局域网 IP>:3081/ws/mobile` | 无需 Helper 或 Nginx;电脑与 iPhone 位于可互访的局域网 | 仅对私有网络放行 TCP `3081`;保持鉴权开启 |
|
|
223
227
|
| 本机 iOS 模拟器 | DSH WebUI 本地入口 | `ws://127.0.0.1:<DSH WebUI 端口>/ws/mobile` | 无需 Helper、Nginx 或独立的 `3081` 端口 | 不开放任何外部端口;仅 Debug 时可关闭鉴权 |
|
|
224
228
|
| Linux 公网服务器 | 插件 Helper + Nginx + TLS | `wss://<服务器公网 IPv4>/ws/mobile` | 执行 `init`,再从 WebUI 填写公网 IPv4 | 云安全组放行 TCP `80/443`;不要公开 DSH 端口和 `3081`;必须鉴权 |
|
|
225
|
-
| 家用 Windows / macOS |
|
|
229
|
+
| 家用 Windows / macOS | Cloudflare 命名 Tunnel | `wss://<公开域名>/ws/mobile` | 在 Cloudflare 建立公开域名到 `127.0.0.1:3082` 的路由;面板填写域名与 Token 并开启 | 无需路由器端口转发;独立入口强制设备鉴权 |
|
|
230
|
+
| 家用 Windows / macOS | Cloudflare Quick Tunnel | `wss://<随机名称>.trycloudflare.com/ws/mobile` | 面板一键开启;重启或重连后地址变化需重新扫码 | 无需域名、账户或端口转发;独立入口强制设备鉴权 |
|
|
231
|
+
| 家用 Windows / macOS | Tailscale Serve | `wss://<Tailscale 域名>/ws/mobile` | 手动转发到 `127.0.0.1:3081`,将生成的地址填入 WebUI | 无需路由器端口转发;保持鉴权开启 |
|
|
226
232
|
|
|
227
233
|
## Linux 服务器公网入口管理
|
|
228
234
|
|
|
@@ -269,6 +275,10 @@ sudo env "PATH=$PATH" npx --yes dsh-plugin-mobile-gateway@latest remove-helper
|
|
|
269
275
|
| iOS 收到 `401` | 在 WebUI 重新生成二维码并配对 |
|
|
270
276
|
| Linux 服务器公网连接超时 | 检查云安全组、服务器防火墙和 TCP `80/443` |
|
|
271
277
|
| Linux 服务器公网地址没有显示 | 在“移动设备 → 公网接入”填写公网 IPv4 并点击更新 |
|
|
278
|
+
| Cloudflare 下载失败 | 检查 PC 到 GitHub Releases 的连接;也可自行安装 cloudflared,并把 `cloudflaredPath` 设为可执行文件的绝对路径 |
|
|
279
|
+
| Quick Tunnel 重启后 App 无法连接 | Quick 地址可能已变化,在面板重新生成二维码并让 App 扫码更新配对;需要固定地址时改用命名 Tunnel |
|
|
280
|
+
| Quick Tunnel 未能启动且 PC 已有 Cloudflare 配置 | Cloudflare 当前不支持 Quick Tunnel 与 `~/.cloudflared/config.yaml` 共用该配置目录;检查面板错误并按 [官方说明](https://developers.cloudflare.com/cloudflare-one/networks/connectors/cloudflare-tunnel/do-more-with-tunnels/trycloudflare/) 处理 |
|
|
281
|
+
| Cloudflare 已连接但 App 无法访问 | 确认公开域名路由的 Service URL 是面板显示的 `http://127.0.0.1:<端口>`,并检查 DNS 与设备配对凭证 |
|
|
272
282
|
| 需要查看服务端日志 | 执行 `tail -f /tmp/mobile-gateway.log` |
|
|
273
283
|
|
|
274
284
|
## 源码开发
|
|
@@ -278,4 +288,4 @@ dsh plugin --profile web add file:/absolute/path/to/dsh-plugin-mobile-gateway
|
|
|
278
288
|
npm test
|
|
279
289
|
```
|
|
280
290
|
|
|
281
|
-
|
|
291
|
+
面板右上角“工具”菜单提供“重启当前网关”和“停止 DSH Web”。重启只在通过 `dsh web` 启动时可用,会结束当前 Web 进程并沿用原启动参数重新运行;重启时自动加入 `--no-open`,不会再打开新的浏览器标签页,当前标签页会等待恢复并刷新。重启后的进程在后台运行,不再由原 Terminal 前台任务管理;需要关闭时可在同一菜单点击“停止 DSH Web”,或在 Terminal 找到监听 Web 端口的 DSH 进程并发送 `SIGTERM`。重启期间连接会短暂中断,Quick Tunnel 的外网地址可能随之改变。源码修改后需重新安装插件并重启 `dsh web`。
|
package/cordis.patch.yml
CHANGED
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
lanEnabled: true
|
|
28
28
|
lanHost: 0.0.0.0
|
|
29
29
|
lanPort: 3081
|
|
30
|
+
# PC only: the UI can enable Quick or named Cloudflare Tunnel.
|
|
31
|
+
# Both use a separate authenticated loopback-only mobile endpoint.
|
|
32
|
+
# cloudflarePort: 3082
|
|
33
|
+
# cloudflaredPath: /path/to/cloudflared
|
|
30
34
|
# Written automatically by `sudo npx dsh-plugin-mobile-gateway setup`.
|
|
31
35
|
publicUrlFile: /etc/dsh-mobile-gateway/public-url
|
|
32
36
|
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# DSH 0.1.7-rc.1 网关与移动应用兼容性审计
|
|
2
|
+
|
|
3
|
+
> 更新(2026-09-24):下文“当前网关”的结论记录的是适配前基线。现已在网关工作树完成 Session V4、`tool/result`、动态权限目录和默认预设配置适配;`dsh-mobile` 已完成 V4 历史解析及 Android/iOS 动态权限选项。网关完整测试、共享层 iOS 模拟器测试、Android 单元测试和 iOS 模拟器构建通过。尚未使用真实 rc.1 会话验证持久历史迁移、SSH 文件访问和运行中归档。新增能力表仍是后续功能规划。
|
|
4
|
+
|
|
5
|
+
核对日期:2026-09-24。网关基线:`3c2ac52`、npm 版本 `0.7.7`。目标:[官方 v0.1.7-rc.1 发布说明](https://github.com/deepseek-ai/deepseek-harness/releases/tag/dsh-v0.1.7-rc.1),发布提交 `46a7f68`。本次为静态审计和一个最小复现;没有升级本机 DSH、迁移会话或修改生产代码。相邻的 `dsh-mobile` 应用仓库有未提交改动,本次只读取了其共享层和 Android 代码。
|
|
6
|
+
|
|
7
|
+
## 结论
|
|
8
|
+
|
|
9
|
+
**当前网关不能支持 v0.1.7-rc.1 的完整会话协议。** 官方 Session 格式是 V4;网关 `lib/dsh-host-adapter.mjs` 固定要求 V3。用 V4 `session/follow` 快照调用生产 `readSessionSnapshot()`,实际抛出 `unsupported-session-format`。因此历史、订阅快照、模型查询和会话预设查询等以快照为前置的功能确定失败。普通 `session/list` 等不读快照的调用可能仍可用,但不能据此判断消息收发全链路可用。
|
|
10
|
+
|
|
11
|
+
V4 的 `tool/result` 消息改为扁平工具角色结构。网关的实时事件映射及应用共享层的原始历史解码仍按 V3 嵌套包装读取,修好版本门槛后还会丢工具结果正文和部分失败状态。之前 0.1.6 审计指出的权限目录问题也仍在。其余 Remote API 与配置语义需要在精确 rc.1 发布包或真实宿主上联调,不能只靠旧测试夹具宣布兼容。
|
|
12
|
+
|
|
13
|
+
## 已确认的协议差异
|
|
14
|
+
|
|
15
|
+
| 优先级 | 差异与证据 | 当前影响 | 改法 |
|
|
16
|
+
|---|---|---|---|
|
|
17
|
+
| P0 | [官方 Session V4 常量](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.7-rc.1/packages/core/session/src/types.ts)为 `4`;网关 `lib/dsh-host-adapter.mjs:17,33` 固定 `3`,`lib/session-follower.mjs:22` 复用该校验 | V4 快照被拒;`history`、实时 follow,以及读取快照的模型、预设状态失败 | 以实际快照 `header.version` 返回格式版本,明确只接收经过验证的版本;同步更新历史/fork 游标校验和 `hello.historyFormatVersion`,让 App 清除旧缓存并重建基线。不能只把常量改成 `4` |
|
|
18
|
+
| P0 | [V3→V4 迁移规则](https://github.com/deepseek-ai/deepseek-harness/blob/dsh-v0.1.7-rc.1/packages/session/session-format-v3-to-v4/README.md):`tool/result` 的 `message.role` 变为 `tool`,结果块直接放在 `message.content`,`isError` 位于 `message.isError` | `lib/index.mjs` 的 `buildWireEvent()` 只遍历旧的 `block.content`,故实时 `preview` 为空;失败判定只看旧包装或 `data.error`。应用 `GatewayDtos.kt` 的原始事件解析同样依赖旧包装 | 网关实时映射同时识别 V4 扁平结果及必要的旧数据;应用原始历史解码支持 V4 `message.content`、`message.isError`,保留 `data.error.code/reason` 供失败展示 |
|
|
19
|
+
| P0 | 旧 [0.1.6 审计](dsh-0.1.6-alpha.1-compatibility-audit.md)已确认候选权限从 `projections.values.permissions.options` 移到 `permissionPresets/catalog`;当前 `lib/index.mjs:1223` 仍要求旧字段 | `command-options(permission)` 与 `command-select(permission)` 失败;旧 `permission-options` 响应不含完整动态候选 | 经 Host Adapter 查询独立目录,与会话 `currentValue` 合并;动态 Auto 候选需使用实时目录,不能写死 |
|
|
20
|
+
| P1 | V3→V4 迁移可能插入中断的 `turn/end` 和子代理目录记录,随后重映射 `seq` 引用;上游负责迁移 | 手机保留 V3 `beforeSeq`、`atSeq`、事件缓存可能指向 V4 的另一位置 | 网关发送真实版本;App 已有格式变更重置机制,需用 V3 缓存升级 V4 的分页、fork 和重连场景验证 |
|
|
21
|
+
| P1 | 官方新增归档运行中任务的确认与 `stopActivity` 语义;网关目前只传 `{sessionId}` | 归档运行中会话可能返回 `workspace/session-active`;App 目前只有直接归档入口 | 透出受影响活动清单和明确的二次确认,再按新版参数请求停止;保留错误详情,不自动强制结束工作 |
|
|
22
|
+
| P1 | `lib/dsh-host-adapter.mjs:16` 写死 `DSH_VERSION='0.1.5-rc.2'`,`lib/index.mjs` 将其放进 `hello` | rc.1 宿主会被误报,诊断和能力判断不可靠 | 从宿主可验证元数据获取实际版本,或把该字段改为明确的网关适配目标,避免声称是运行版本 |
|
|
23
|
+
|
|
24
|
+
版本含义需分清:`dsh-mobile-v1` 是 WebSocket 子协议,`hello.protocol=3` 是网关协议,`Session header.version=4` 是上游持久日志格式;升级 Session 格式不必机械地改变前两者。
|
|
25
|
+
|
|
26
|
+
## 需在 rc.1 宿主复核的范围
|
|
27
|
+
|
|
28
|
+
- `session/create`、`prompt`、`follow/page/control`、`commands/execute`、`settings/describe/update`、`agentPresets/list/select`、`workspace/follow/archiveSession` 的精确参数和返回形状。发布说明明确客户端 Session 多实例及 slot 改变,但本插件使用 Host Remote Gateway;不能直接认定客户端 slot 变化作用于这里,也不能未经发布包检查就认定无影响。
|
|
29
|
+
- 设置已改为当前 Profile 的插件配置,Agent 预设改由插件组合包声明。网关的 `defaults` 与 `set-default` 假设旧 `agent-presets`、`permission` namespace 和字段;需要核实生效默认值、可修改字段和实时更新结果。此前 `modeSelectionEnabled=false` 时保存值不等于实际默认值的问题仍需处理。
|
|
30
|
+
- 新版归档、置顶、恢复的 Workspace Remote 能力,以及列表和 `workspace/follow` 的增量形状。当前只转发 `archivedSessionIds`,没有恢复或置顶入口。
|
|
31
|
+
- 真实升级场景:V3 持久会话迁移成 V4 后的 seq、工具结果、失败尝试、子代理记录、活动流快照、历史分页、fork 和应用缓存重建。当前 `npm test` 的 Host 假数据仍以 V3 为主,不能证明 rc.1 兼容。
|
|
32
|
+
- SSH 工作区的文件读取。当前 `lib/index.mjs` 用本机 `realpath/readdir` 和文件句柄实现 `file-list`、下载,不支持新版 SSH 文件提供方;需要改走 Host 文件能力或在远程工作区禁用相关入口。
|
|
33
|
+
|
|
34
|
+
## 可添加到网关与应用的功能
|
|
35
|
+
|
|
36
|
+
| 顺序 | 能力 | 网关工作 | 移动应用工作 |
|
|
37
|
+
|---|---|---|---|
|
|
38
|
+
| 1 | 归档管理:列表、筛选、恢复、置顶 | 接入 Workspace 归档/恢复/置顶 Remote 方法、状态流及运行中归档确认 | 增加归档页、置顶排序、恢复操作和受影响任务确认 |
|
|
39
|
+
| 2 | 后台任务与工作流 | 接入 Job Controller 的任务列表、实时输出、取消/唤醒状态 | 增加任务面板和完成通知;与现有会话重连状态对齐 |
|
|
40
|
+
| 3 | 文件改动审阅 | 提供经 Session 授权的文件改动清单与 diff,按需分页或分块传输 | 增加文件卡片、逐行/并排 diff,支持从聊天定位;先做文本 diff |
|
|
41
|
+
| 4 | 文件与文档预览 | 改用 Host `readBytes`/二进制流能力,避免本机 `fs` 对 SSH 路径的错误映射;继续做路径授权和流量限制 | 在现有文件浏览/下载基础上增加图片、PDF、Office、CSV/TSV 预览及缩放 |
|
|
42
|
+
| 5 | Subagent/Team 可观测性 | 透出成员、任务和子会话标识及更新流,保持看板只读 | 展示成员状态、切换子会话、跳转任务,不提供直接篡改 Agent 看板的 UI |
|
|
43
|
+
| 6 | 终端与浏览器侧栏 | 若要远程交互,新增单独鉴权的终端/浏览器会话接口、显式授权与生命周期管理 | 多标签终端、Shell 选择、URL 打开和会话恢复;属于高权限功能,应单独设计 |
|
|
44
|
+
| 7 | 插件管理与 MCP 资源 | 接入插件安装/启停配置及 MCP 资源列表/读取,分离只读与写操作 | 可先做资源浏览,再做插件管理;展示兼容性检查和安装任务状态 |
|
|
45
|
+
|
|
46
|
+
Headless `--json`、Messages/Files API、MCP SDK v2、PTC 命名/隔离、Browser/Computer Use、Auto review 等多数变化发生在宿主内部。网关无需复制其底层协议;若要让手机操作或展示这些功能,应新增明确的 Remote 映射和移动端能力声明。对涉及终端、插件安装、浏览器和 Computer Use 的入口,沿用现有设备认证还不够,需要单独核定授权边界。
|
|
47
|
+
|
|
48
|
+
## 验证记录
|
|
49
|
+
|
|
50
|
+
最小复现输入 `readSessionSnapshot({type:'snapshot', header:{version:4,id:'s1'}, cursor:0, records:[], projections:{}}, 's1')`,实际输出:`unsupported-session-format: mobile-gateway requires DSH 0.1.5-rc.2 Session format 3`。
|
|
51
|
+
|
|
52
|
+
本次没有可连接的 rc.1 宿主;本机 `git ls-remote` 因网络代理不可达而失败。因此本文将源码可确定的阻断与待联调项目分开,不把旧单元测试通过等同于新版兼容。
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# DSH 0.2.0-rc.2 插件管理:DSH Mobile 对接方案
|
|
2
|
+
|
|
3
|
+
## 范围与入口
|
|
4
|
+
|
|
5
|
+
网关通过现有 `dsh-mobile-v1` WebSocket 提供与 DSH Web 插件页相同的管理数据和操作。所有请求发往 **control** 连接;单连接旧客户端也可发送。必须使用已配对设备的 token。`hello.capabilities` 包含 `plugin-management-v1` 表示网关认识这些帧,页面仍以 `plugin-catalog.managementAvailable` 判断当前 Host 能否管理插件。网关修改的是其所连接的 DSH profile,不是手机本地插件。
|
|
6
|
+
|
|
7
|
+
截图中的「添加插件」对应**安装已有插件包**,不是创建源码工程。DSH RC2 的 Host 没有远程生成插件工程的接口;本方案覆盖 Web 插件页的浏览、安装、启停、卸载、配置与诊断,以及 Host 公开的插件条目控制和版本豁免。
|
|
8
|
+
|
|
9
|
+
## 页面数据
|
|
10
|
+
|
|
11
|
+
连接后发送:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{"type":"plugin-catalog","requestId":"catalog-1"}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
成功返回 `kind: plugin-catalog`、相同 `requestId`,以及:
|
|
18
|
+
|
|
19
|
+
| 字段 | 含义 |
|
|
20
|
+
|---|---|
|
|
21
|
+
| `managementAvailable` | 当前 Host profile 是否支持插件管理;false 时隐藏写操作 |
|
|
22
|
+
| `bundles` | Host `pluginManager/listBundles` 数组;每项额外增加网关计算的 `experimental` 布尔值 |
|
|
23
|
+
| `plugins` | Host `pluginManager/listPlugins` 原始数组;用于包内条目及独立条目控制 |
|
|
24
|
+
| `officialItems` | Web 的四类内置配置卡片:`shell`、`agent-loop`、`subagent`、`web-search`;每项提供当前可用的 `namespaces` |
|
|
25
|
+
| `settingsWritable` | Host 设置是否允许写入 |
|
|
26
|
+
|
|
27
|
+
按 Web 页面语义展示:先排除 DSH 内置 profile 包 `@deepseek-ai/dsh-base`、`@deepseek-ai/dsh-web-app`、`@deepseek-ai/dsh-headless`、`@deepseek-ai/dsh-sdk-app`、`@deepseek-ai/dsh-acp-app`、`@deepseek-ai/dsh-sdk-minimal`,再保留 `installed || optional || error` 的 bundle。其中 `optional && !installed` 放在「官方」,`installed || !optional` 放在「已安装」。内置配置卡片也放在「官方」,但没有包级启停开关,点击进入配置。`bundles` 每行有 `name`、`version?`、`meta?`、`description?`、`enabled`、`installed`、`optional`、`removable`、`readOnlyReason?`、`error?`、`rows`、`overrides`,以及网关追加的 `experimental`。DSH Web 根据包名以 `@deepseek-ai/dsh-experimental-` 开头来生成「实验性」标签;移动端直接用 `experimental: true` 显示本地化标签,不需要自行猜测。显示名称、说明和图标优先取 `meta` 的本地化字段;`meta.title` 和 `meta.description` 可以是文本或带 `en` 回退的语言映射,`meta.icon` 是图片 data URL,不作为 HTML 注入。否则回退到 `description` / `name`。`readOnlyReason` 禁用启停,`removable: false` 仅禁用卸载;官方可选包即使不可卸载仍可启停。不要用 `plugins` 推测一个 bundle 的总开关状态,以 `bundle.enabled` 为准。
|
|
28
|
+
|
|
29
|
+
`managementAvailable: false` 时返回空目录和 `settingsWritable: false`。所有目录数据属于所连网关的 DSH profile;切换网关后重新请求并按 `gatewayId` 分别缓存。
|
|
30
|
+
|
|
31
|
+
## 请求与响应
|
|
32
|
+
|
|
33
|
+
每个插件请求都必须带本次连接内唯一的非空 `requestId`;响应原样带回。成功帧如下,`result` 均为 Host 原始结果,App 不应只凭发送成功就更新开关:
|
|
34
|
+
|
|
35
|
+
| 请求 `type` | 参数 | 成功 `kind` 与字段 |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `plugin-catalog` | — | `plugin-catalog`:上述目录字段 |
|
|
38
|
+
| `plugin-registries` | — | `plugin-registries`:`registry`、`fallbackRegistries`、`resolved` |
|
|
39
|
+
| `plugin-inspect` | `spec`, `registry?` | `plugin-inspection`:`result` 为 `accepted` 或 `refused` 的安装前检查 |
|
|
40
|
+
| `plugin-install` | `spec`, `enabled?`, `registry?`, `approvedBuilds?` | `plugin-install-result`:`result` 为 `ChangeResult` |
|
|
41
|
+
| `plugin-install-status` | `installId` | `plugin-install-status`:`result` 为 `ChangeResult` 或 `null` |
|
|
42
|
+
| `plugin-install-cancel` | `installId` | `plugin-install-cancel`:`result.status` 为 `cancelled` / `too-late` / `not-running` |
|
|
43
|
+
| `plugin-bundle-set-enabled` | `name`, `enabled` | 同名 `kind`:`result` 为 `ChangeResult` |
|
|
44
|
+
| `plugin-entry-set-enabled` | `entryId`, `enabled` | 同名 `kind`:`result` 为 `ChangeResult` |
|
|
45
|
+
| `plugin-remove` | `name` | 同名 `kind`:`result` 为 `ChangeResult` |
|
|
46
|
+
| `plugin-settings` | `ns?` | 同名 `kind`:不传 `ns` 返回 `writable`、`hasDocument`、`namespaces`;传 `ns` 返回 `writable`、`namespace` |
|
|
47
|
+
| `plugin-settings-mutate` | `ns`, `expectedRevision`, `ops` | 同名 `kind`:返回修改后的 namespace 视图 |
|
|
48
|
+
| `plugin-version-exemptions` | — | 同名 `kind`:`exemptions`、`warnings` |
|
|
49
|
+
| `plugin-version-exemption-set` | `packageVersion`, `runtimeVersion`, `enabled`, `acceptRisk?` | 同名 `kind`:`result` 为 `ChangeResult` |
|
|
50
|
+
|
|
51
|
+
`spec` 是 DSH/pnpm 安装规格,可为包名、绝对本地路径、Git 或 tarball。`registry` 为 HTTP(S) 地址或 `null`(使用 pnpm 自身配置),省略则按 Host 当前配置。移动端先调用 `plugin-registries` 提供来源选择,再 `plugin-inspect` 展示包名、版本、来源、是否 bundle;只有 `status: accepted` 才提交安装。包内插件条目使用 `entryId`,包级操作使用 `name`,二者不可互换。
|
|
52
|
+
|
|
53
|
+
### 安装与恢复
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{"type":"plugin-inspect","requestId":"inspect-1","spec":"some-dsh-plugin"}
|
|
57
|
+
{"type":"plugin-install","requestId":"install-1","spec":"some-dsh-plugin"}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
安装期间,同一控制连接收到:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{"kind":"plugin-install-state","requestId":"install-1","phase":"installing","attempt":{"registry":null,"index":1,"total":1}}
|
|
64
|
+
{"kind":"plugin-install-log","requestId":"install-1","jobId":"...","stream":"stdout","text":"..."}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`phase` 可为 `installing`、`cancelling`、`applying`。日志只发给发起安装的连接,包含 `jobId`、`stream`、`text` 和可选 `exitCode`;App 限制内存中日志行数,并对输出视为不可信文本。结束时收到 `plugin-install-result`。取消使用 `plugin-install-cancel`,其 `installId` 等于原安装请求的 `requestId`。断线重连后可发送 `plugin-install-status`,`installId` 仍为原安装 ID;它可能等待运行中的安装结束,`result: null` 表示 Host 无此记录,随后刷新目录。不要用新的 `plugin-install` 盲目重试。
|
|
68
|
+
|
|
69
|
+
`ChangeResult.application` 可能是 `applied`、`restart-required`、`overridden`、`failed`、`cancelled`;还需查看 `changed`、`stage`、`error`、`warnings`、`pendingBuilds`、`approvedBuilds`、`packageResult`。`restart-required` 时提示重启 DSH Web 后再刷新目录。若 `pendingBuilds` 非空,只有用户明确批准这些具体包的构建脚本,才再次安装并将包名放入 `approvedBuilds`;切勿自动批准。失败或取消后刷新目录确认最终状态。
|
|
70
|
+
|
|
71
|
+
### 开关、卸载与配置
|
|
72
|
+
|
|
73
|
+
```json
|
|
74
|
+
{"type":"plugin-bundle-set-enabled","requestId":"toggle-1","name":"some-dsh-plugin","enabled":false}
|
|
75
|
+
{"type":"plugin-entry-set-enabled","requestId":"entry-1","entryId":"some-entry-id","enabled":true}
|
|
76
|
+
{"type":"plugin-remove","requestId":"remove-1","name":"some-dsh-plugin"}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
用户操作前用目录的 `readOnlyReason`、`removable` 决定控件可用性。收到结果后依 `application` 展示成功、重启要求或错误,再刷新 `plugin-catalog`。**关闭或卸载 `dsh-plugin-mobile-gateway` 自身会切断当前连接**;App 应在操作前明确提示,并把连接中断后的最终状态标为“待重连确认”,不可视作操作失败或成功。关闭后只能从 DSH Web/CLI 重新开启。
|
|
80
|
+
|
|
81
|
+
配置卡片与包内配置都走 Host settings。先请求 `plugin-settings`,用 `namespace.schema` 渲染表单,展示 `value` / `base` / `user`,使用 `secrets[].set` 显示“已设置”,不得期待服务端回传密钥明文。保存时仅提交改动路径:
|
|
82
|
+
|
|
83
|
+
```json
|
|
84
|
+
{"type":"plugin-settings-mutate","requestId":"save-1","ns":"web-search-deepseek","expectedRevision":4,"ops":[{"op":"set","path":["provider"],"value":"example"}]}
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
`ops` 支持 `set`(需要 `value`)和 `unset`,`path` 为字符串数组,空数组表示整个配置节。移动端应优先提交字段级操作并带读取时的 `revision`;版本冲突重新获取 namespace,让用户决定如何合并。内置配置卡片的 namespace 由 `officialItems` 返回,不要把 Web UI 内部路由当成插件 ID。
|
|
88
|
+
|
|
89
|
+
版本豁免属于高风险高级设置;只在用户看到 Host 的具体不兼容版本并明确接受风险时传 `acceptRisk: true`。普通插件页可以先只展示豁免情况,但协议已经支持读取与修改。
|
|
90
|
+
|
|
91
|
+
## 更新通知、错误与安全
|
|
92
|
+
|
|
93
|
+
| 服务端通知 | App 行为 |
|
|
94
|
+
|---|---|
|
|
95
|
+
| `plugins-changed`,含 `reason` (`plugin` / `bundle` / `install` / `remove`) | 重新请求 `plugin-catalog`;该通知不包含完整目录 |
|
|
96
|
+
| `plugin-settings-changed`,含 `ns`、`revision` | 当前正在编辑该 namespace 时标记远端已更新,重新读取后再保存 |
|
|
97
|
+
| `plugin-install-state` / `plugin-install-log` | 仅更新匹配原安装 `requestId` 的进度 UI |
|
|
98
|
+
|
|
99
|
+
错误统一为 `{"kind":"error","requestType":"...","requestId":"...","code":"...","message":"..."}`。参数错误为 `bad-request`,未配对为 `authentication-required`,同 ID 安装进行中为 `plugin-install/in-progress`;Host 错误保留其 `code`。`plugin-inspect` 的业务拒绝是成功帧内 `result.status: refused`,不是传输错误。所有可执行操作只发至经过鉴权的控制连接,App 不应在未配对、只读或目录不可用时展示可用按钮。
|
|
100
|
+
|
|
101
|
+
## DSH Mobile 实施顺序与验收
|
|
102
|
+
|
|
103
|
+
1. 在协议模型和控制通道请求器加入上表帧;`requestId` 用 UUID,按 `(gatewayId, requestId)` 关联响应及安装事件,设置超时和断线恢复。
|
|
104
|
+
2. 完成插件页:官方可选包、四个内置配置卡片、已安装第三方包;使用 Host 提供的本地化元数据、状态与只读原因。
|
|
105
|
+
3. 完成「添加插件」流程:registries → inspect → 用户确认 → install → 进度/日志 → 结果/刷新;支持取消和重连查询。
|
|
106
|
+
4. 完成包级启停、条目级启停、卸载和 schema 配置;对 `restart-required`、构建脚本批准、网关自身停用及配置冲突给出明确状态。
|
|
107
|
+
5. 在已配对 RC2 环境验收:官方包开关、第三方包安装和卸载、配置读取/保存、安装取消、断线恢复;同时验证未配对控制连接被拒绝。
|
|
108
|
+
|
|
109
|
+
网关源码的单元测试覆盖 Host Remote 参数映射、响应、校验及安装 ID;端到端的真实包安装仍应在非生产 DSH profile 中验收,避免修改用户当前运行的 Web profile。
|
|
@@ -57,12 +57,13 @@
|
|
|
57
57
|
|
|
58
58
|
## 3. 地址语义与信任规则
|
|
59
59
|
|
|
60
|
-
`publicUrl` 是本次配对的首选地址,兼容旧 App。`endpoints` 的来源顺序为:首选地址 → 本次本机配对请求指定的地址 → 插件配置地址 → 已配置公网地址 → 已启动局域网监听的地址。管理状态没有本次配对地址,只返回配置与监听产生的候选地址。
|
|
60
|
+
`publicUrl` 是本次配对的首选地址,兼容旧 App。`endpoints` 的来源顺序为:首选地址 → 本次本机配对请求指定的地址 → 插件配置地址 → 已配置公网地址 → 已取得的 Cloudflare Tunnel 地址 → 已启动局域网监听的地址。管理状态没有本次配对地址,只返回配置与监听产生的候选地址。
|
|
61
61
|
|
|
62
62
|
- `http` / `https` 输入由插件规范化为 `ws` / `wss`。不允许用户名密码、查询参数、fragment 或 `0.0.0.0` / `::` 监听地址;公网明文 WS 被拒绝。
|
|
63
63
|
- 每个输入列表最多 16 项,每项最多 2048 字符;合并后超过 16 个也会拒绝。候选地址可能包含手机不可达的 loopback 地址,App 应过滤或提示,不可据此判断网关离线。
|
|
64
64
|
- 地址顺序不保证网络可达或代表实时延迟;App 可以优先尝试最近成功且已确认可信的地址。
|
|
65
65
|
- 只有来自用户认可的配对二维码/手动导入,或用户单独确认的地址,才可使用凭证。`gatewayId` 是关联标识,不是证书或密码学身份凭据。
|
|
66
|
+
- Quick Tunnel 每次启动或重连可能取得新的 `trycloudflare.com` 地址;旧地址不能自动推导新地址。App 应让用户重新扫码更新同一 `gatewayId` 的已确认地址,不能向未确认的随机域名自动发送旧 Token。
|
|
66
67
|
- `hello` 在鉴权之后返回,不能依赖“先发送 token、再比较 ID”保护 token 不被恶意端点窃取;TLS 校验和发送前的地址信任判断必须先完成。局域网明文 WS 仅用于用户信任的网络。
|
|
67
68
|
- 地址被重定向时不得把 token 自动转发到其他来源。
|
|
68
69
|
- 配对码是单次使用。候选地址上的配对请求必须串行;若发生网络断开且无法确定配对是否已被消费,应重新生成二维码,不能对多个地址并行消耗同一码。
|