dsh-plugin-mobile-gateway 0.4.2 → 0.6.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 +211 -17
- package/README.md +11 -4
- package/docs/assets/whale-girl-ios-app-promo-16x9.png +0 -0
- package/docs/blog-mobile-gateway.md +542 -0
- package/docs/public-ip-deployment-guide.md +135 -0
- package/lib/client.js +8 -3
- package/lib/index.mjs +228 -13
- package/package.json +1 -1
package/PROTOCOL.md
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
# dsh Mobile Gateway — WebSocket 协议参考
|
|
2
2
|
|
|
3
|
-
移动端通过一个经过设备鉴权的 WebSocket 连接与 dsh 通信:订阅 agent
|
|
3
|
+
移动端通过一个经过设备鉴权的 WebSocket 连接与 dsh 通信:订阅 agent 实时输出、发送文字和图片、处理 Human-in-the-loop 提问、查询会话/工作区/历史、调整会话配置。本协议由持久化插件 `dsh-plugin-mobile-gateway` 实现(v0.6.0)。
|
|
4
4
|
|
|
5
5
|
- **本机端点**:`ws://127.0.0.1:3080/ws/mobile`(与 dsh web GUI 同端口)
|
|
6
6
|
- **局域网端点**:`ws://<电脑的私有局域网 IP>:3081/ws/mobile`(插件独立监听,只提供经过鉴权的 WebSocket)
|
|
7
7
|
- **公网端点**:必须由 TLS 反向代理提供 `wss://<域名>/ws/mobile`
|
|
8
|
-
- **帧格式**:全部为 JSON 文本帧(UTF-8
|
|
8
|
+
- **帧格式**:全部为 JSON 文本帧(UTF-8);图片字节使用标准 Base64
|
|
9
9
|
- **连接即推送**:连上后服务端立刻发送一条 `hello`,之后 agent 输出以 `event` 帧实时推送
|
|
10
10
|
|
|
11
11
|
---
|
|
@@ -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":
|
|
80
|
+
{ "kind": "hello", "protocol": 3, "capabilities": ["images"], "authenticated": true,
|
|
81
81
|
"device": { "id": "...", "name": "iPhone" }, "port": 3080, "clients": 1 }
|
|
82
82
|
```
|
|
83
83
|
|
|
@@ -151,7 +151,111 @@ func connectAuthenticated(publicURL: URL, token: String) -> URLSessionWebSocketT
|
|
|
151
151
|
|
|
152
152
|
---
|
|
153
153
|
|
|
154
|
-
## 3.
|
|
154
|
+
## 3. Human-in-the-loop 提问与回答
|
|
155
|
+
|
|
156
|
+
Agent 调用 DSH 的 `ask_user_question` 工具时,插件通过 API Gateway 的 `events.mux()` 收到临时的待回答请求,并推送给移动端。该请求不属于持久化的 `session/event`;回答必须使用本节协议,不能作为普通 `message` 发送。
|
|
157
|
+
|
|
158
|
+
### `question-requested` — 服务端推送问题
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"kind": "question-requested",
|
|
163
|
+
"rpcId": "5ce4f5d1-...",
|
|
164
|
+
"sessionId": "session-abc",
|
|
165
|
+
"questions": [
|
|
166
|
+
{
|
|
167
|
+
"id": "research-direction",
|
|
168
|
+
"header": "研究方向",
|
|
169
|
+
"question": "你想深入研究哪个方向?",
|
|
170
|
+
"detail": "请选择最感兴趣的方向",
|
|
171
|
+
"options": [
|
|
172
|
+
{ "label": "核心架构", "description": "DSH CLI、profile、bundle 与 Cordis" },
|
|
173
|
+
{ "label": "移动网关", "description": "研究 iOS 与 WebSocket 插件" }
|
|
174
|
+
],
|
|
175
|
+
"multiSelect": false
|
|
176
|
+
}
|
|
177
|
+
]
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
- `rpcId`:API Gateway 为这一整批问题生成的稳定 ID。回答或取消时必须原样返回,客户端不得自行生成。
|
|
182
|
+
- `questions`:一次工具调用中的完整问题批次;可能包含多题。
|
|
183
|
+
- `id`:问题 ID,必须在对应答案中原样返回。
|
|
184
|
+
- `header` / `detail`:可选展示信息。
|
|
185
|
+
- `options`:可选列表;每项包含 `label` 和可选 `description`。
|
|
186
|
+
- `multiSelect`:`true` 允许多选,缺省或 `false` 为单选。
|
|
187
|
+
- `intent`:可选展示意图。目前可能为 `{ "kind":"plan-review", "approve":"批准选项标签" }`;未知 intent 应退化为普通选项列表。
|
|
188
|
+
- `replay: true`:可选。表示这是移动端连接后重放的仍待回答问题。iOS 必须按 `rpcId` 去重。
|
|
189
|
+
|
|
190
|
+
### `question-answer` — 移动端提交整批答案
|
|
191
|
+
|
|
192
|
+
```json
|
|
193
|
+
{
|
|
194
|
+
"type": "question-answer",
|
|
195
|
+
"rpcId": "5ce4f5d1-...",
|
|
196
|
+
"sessionId": "session-abc",
|
|
197
|
+
"answers": [
|
|
198
|
+
{
|
|
199
|
+
"id": "research-direction",
|
|
200
|
+
"selected": ["移动网关"]
|
|
201
|
+
}
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
自由输入使用 `custom`。单选题使用 `custom` 时 `selected` 必须为空;多选题可以同时携带两者:
|
|
207
|
+
|
|
208
|
+
```json
|
|
209
|
+
{
|
|
210
|
+
"id": "research-direction",
|
|
211
|
+
"selected": [],
|
|
212
|
+
"custom": "我想研究 API Gateway 的安全边界"
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
提交规则由 API Gateway 严格校验:
|
|
217
|
+
|
|
218
|
+
- 必须一次提交这一批中的全部问题,`answers` 数量、顺序和 `id` 必须与 `questions` 一致。
|
|
219
|
+
- `selected` 中的值必须与原始 `options[].label` 完全一致,且不能重复。
|
|
220
|
+
- 单选题最多选择一项;单选题的 `custom` 与 `selected` 互斥。
|
|
221
|
+
- `custom` 如果存在,去除首尾空白后不能是空字符串。
|
|
222
|
+
|
|
223
|
+
插件立即返回交付回执:
|
|
224
|
+
|
|
225
|
+
```json
|
|
226
|
+
{ "kind":"question-response", "rpcId":"5ce4f5d1-...", "sessionId":"session-abc",
|
|
227
|
+
"action":"answer", "accepted":true }
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
如果 WebUI 或另一台移动设备已经先回答:
|
|
231
|
+
|
|
232
|
+
```json
|
|
233
|
+
{ "kind":"question-response", "rpcId":"5ce4f5d1-...", "sessionId":"session-abc",
|
|
234
|
+
"action":"answer", "accepted":false, "reason":"not-pending" }
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
答案结构不合法时 `reason` 为 `bad-response`。这两种情况均不能重发为普通聊天消息。
|
|
238
|
+
|
|
239
|
+
### `question-cancel` — 跳过/取消整批问题
|
|
240
|
+
|
|
241
|
+
```json
|
|
242
|
+
{ "type":"question-cancel", "rpcId":"5ce4f5d1-...", "sessionId":"session-abc" }
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
回执仍为 `question-response`,其中 `action` 为 `cancel`。取消会让等待中的 `ask_user_question` 以 `ASK_CANCELLED` 结束,iOS 应在用户确认后再执行。
|
|
246
|
+
|
|
247
|
+
### `question-resolved` — 服务端广播最终状态
|
|
248
|
+
|
|
249
|
+
```json
|
|
250
|
+
{ "kind":"question-resolved", "rpcId":"5ce4f5d1-...", "sessionId":"session-abc",
|
|
251
|
+
"outcome":"answered" }
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
`outcome` 为 `answered` 或 `cancelled`。WebUI、iOS 或其他客户端中的第一个合法响应获胜;所有移动连接都会收到最终状态并应关闭对应选择界面。移动端断线重连后,API Gateway 会重放仍待回答的问题;DSH 进程重启则会取消这些仅存在于运行时的问题。
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## 4. 消息(手机 → agent)
|
|
155
259
|
|
|
156
260
|
### `message` — 发送消息(会话不存在则创建)
|
|
157
261
|
```json
|
|
@@ -161,6 +265,48 @@ func connectAuthenticated(publicURL: URL, token: String) -> URLSessionWebSocketT
|
|
|
161
265
|
- `sessionId`:可选。省略时**自动创建新会话**(可用 `workspaceId` 或 `cwd` 指定归属工作区,至多一个,workspaceId 优先)
|
|
162
266
|
- `mode`:`"queue"`(排队,默认)/ `"steer"`(打断当前回合)
|
|
163
267
|
- `text` 以 `/` 开头会被当作**斜杠命令**(如 `/permission ask`),宿主直接执行、**绝不发给模型**
|
|
268
|
+
- `text` 与 `images` 至少提供一项;因此支持纯图片消息
|
|
269
|
+
- `clientTimeZone`:可选 IANA 时区,例如 `Asia/Shanghai`,宿主会校验后记录到这条用户消息
|
|
270
|
+
|
|
271
|
+
### 发送图片
|
|
272
|
+
|
|
273
|
+
iOS 将本地图片原始文件数据编码成**标准 Base64**,不要包含 `data:image/...;base64,` 前缀:
|
|
274
|
+
|
|
275
|
+
```json
|
|
276
|
+
{
|
|
277
|
+
"type": "message",
|
|
278
|
+
"sessionId": "session-abc",
|
|
279
|
+
"text": "请描述这两张图片",
|
|
280
|
+
"clientTimeZone": "Asia/Shanghai",
|
|
281
|
+
"images": [
|
|
282
|
+
{
|
|
283
|
+
"mediaType": "image/jpeg",
|
|
284
|
+
"data": "/9j/4AAQSkZJRgABAQ...",
|
|
285
|
+
"name": "IMG_1024.JPG"
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
"mediaType": "image/png",
|
|
289
|
+
"data": "iVBORw0KGgoAAA...",
|
|
290
|
+
"name": "diagram.png"
|
|
291
|
+
}
|
|
292
|
+
]
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
支持的 `mediaType`:`image/png`、`image/jpeg`、`image/webp`、`image/gif`。宿主会验证 Base64、文件签名、格式、尺寸、像素数、单图大小、图片数量和总大小;声明 MIME 与真实字节不一致会拒绝整条消息,且不会产生部分附件。
|
|
297
|
+
|
|
298
|
+
当前 DSH 默认最多 20 张图片、单图约 3.5 MiB、单条消息图片总计 100 MiB,实际值以最近一次 `history.projections.values.imageLimits` 为准。WebSocket 单帧上限默认 144 MiB,用于容纳 100 MiB 图片经 Base64 后的 JSON 请求;反向代理也必须允许相应大小的 WebSocket 帧。
|
|
299
|
+
|
|
300
|
+
Swift 编码示例:
|
|
301
|
+
|
|
302
|
+
```swift
|
|
303
|
+
let data = try Data(contentsOf: imageURL)
|
|
304
|
+
let image = [
|
|
305
|
+
"mediaType": "image/jpeg",
|
|
306
|
+
"data": data.base64EncodedString(),
|
|
307
|
+
"name": imageURL.lastPathComponent
|
|
308
|
+
]
|
|
309
|
+
```
|
|
164
310
|
|
|
165
311
|
```json
|
|
166
312
|
→ { "kind": "sent", "sessionId": "session-abc", "mode": "queue" }
|
|
@@ -169,12 +315,13 @@ func connectAuthenticated(publicURL: URL, token: String) -> URLSessionWebSocketT
|
|
|
169
315
|
|
|
170
316
|
---
|
|
171
317
|
|
|
172
|
-
##
|
|
318
|
+
## 5. 会话与历史查询
|
|
173
319
|
|
|
174
320
|
| type | 参数 | 说明 |
|
|
175
321
|
|---|---|---|
|
|
176
322
|
| `sessions` | — | 会话列表(`updatedAt/running/blank/cwd/agentPreset`) |
|
|
177
323
|
| `history` | `sessionId`, `beforeSeq?`, `maxMessages?`, `maxBytes?`, `view?` | 历史事件页(见下) |
|
|
324
|
+
| `attachment` | `sessionId`, `attachmentId` | 读取历史中属于该会话的图片字节 |
|
|
178
325
|
| `search` | `query` | 会话全文搜索 |
|
|
179
326
|
| `session-stats` | `sessionId` | 执行统计投影(输入框统计条数据源) |
|
|
180
327
|
| `context-usage` | `sessionId` | token 用量 + 上下文占用投影 |
|
|
@@ -184,6 +331,7 @@ func connectAuthenticated(publicURL: URL, token: String) -> URLSessionWebSocketT
|
|
|
184
331
|
{ "type": "history", "sessionId": "session-abc", "maxMessages": 60, "maxBytes": 4194304, "view": "conversation" }
|
|
185
332
|
```
|
|
186
333
|
- 返回**原始 SessionEvent**(`{type, seq, time, data}`,方案A),可选裁剪
|
|
334
|
+
- 图片不会内联进历史页。`user/message.data.content[]` 中的图片块为 `{ "type":"image", "attachment": ImageAttachmentRef }`;iOS 使用其中的 `attachmentId` 请求图片数据
|
|
187
335
|
- `maxBytes`:单帧字节预算,默认 **4 MiB**;超预算保留最新部分并给出 `nextBeforeSeq` 续页(客户端 16 MiB 上限的安全余量)
|
|
188
336
|
- `view: "conversation"`:**对话裁剪模式**——丢弃 `assistant/chunk`(token 回放)与 `request/header`(system prompt),`tool/result` 嵌套文本截断到 2000 字符
|
|
189
337
|
- 分页:`hasMore` 为真时用 `beforeSeq: nextBeforeSeq` 请求更早一页
|
|
@@ -194,6 +342,48 @@ func connectAuthenticated(publicURL: URL, token: String) -> URLSessionWebSocketT
|
|
|
194
342
|
"projections": { "asOfSeq": 127, "values": { "tokenUsage": {...}, "contextPressure": {...}, "permissions": {...}, "sessionStats": {...} } } }
|
|
195
343
|
```
|
|
196
344
|
|
|
345
|
+
图片引用结构:
|
|
346
|
+
|
|
347
|
+
```json
|
|
348
|
+
{
|
|
349
|
+
"type": "image",
|
|
350
|
+
"attachment": {
|
|
351
|
+
"attachmentId": "sha256-opaque-id",
|
|
352
|
+
"mediaType": "image/jpeg",
|
|
353
|
+
"bytes": 184320,
|
|
354
|
+
"width": 1200,
|
|
355
|
+
"height": 900,
|
|
356
|
+
"name": "IMG_1024.JPG"
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
iOS 发现尚未缓存的 `attachmentId` 后发送:
|
|
362
|
+
|
|
363
|
+
```json
|
|
364
|
+
{ "type":"attachment", "sessionId":"session-abc", "attachmentId":"sha256-opaque-id" }
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
服务端在确认该会话历史确实引用了这张图片后返回:
|
|
368
|
+
|
|
369
|
+
```json
|
|
370
|
+
{
|
|
371
|
+
"kind": "attachment",
|
|
372
|
+
"sessionId": "session-abc",
|
|
373
|
+
"attachment": {
|
|
374
|
+
"attachmentId": "sha256-opaque-id",
|
|
375
|
+
"mediaType": "image/jpeg",
|
|
376
|
+
"bytes": 184320,
|
|
377
|
+
"width": 1200,
|
|
378
|
+
"height": 900,
|
|
379
|
+
"name": "IMG_1024.JPG"
|
|
380
|
+
},
|
|
381
|
+
"data": "/9j/4AAQSkZJRgABAQ..."
|
|
382
|
+
}
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
iOS 用 `Data(base64Encoded:)` 解码并按 `attachment.mediaType` 渲染,建议以 `attachmentId` 为缓存键。不要把 Base64 长期保存在对话模型对象里。并发同步历史时可限制为 2~4 个附件请求,优先加载当前可见消息。
|
|
386
|
+
|
|
197
387
|
### `session-stats` 详细(输入框统计条)
|
|
198
388
|
```json
|
|
199
389
|
{ "type": "session-stats", "sessionId": "session-abc" }
|
|
@@ -208,7 +398,7 @@ func connectAuthenticated(publicURL: URL, token: String) -> URLSessionWebSocketT
|
|
|
208
398
|
|
|
209
399
|
---
|
|
210
400
|
|
|
211
|
-
##
|
|
401
|
+
## 6. 工作区与目录
|
|
212
402
|
|
|
213
403
|
| type | 参数 | 说明 |
|
|
214
404
|
|---|---|---|
|
|
@@ -224,7 +414,7 @@ func connectAuthenticated(publicURL: URL, token: String) -> URLSessionWebSocketT
|
|
|
224
414
|
|
|
225
415
|
---
|
|
226
416
|
|
|
227
|
-
##
|
|
417
|
+
## 7. 模型与思考等级
|
|
228
418
|
|
|
229
419
|
| type | 参数 | 说明 |
|
|
230
420
|
|---|---|---|
|
|
@@ -256,7 +446,7 @@ func connectAuthenticated(publicURL: URL, token: String) -> URLSessionWebSocketT
|
|
|
256
446
|
|
|
257
447
|
---
|
|
258
448
|
|
|
259
|
-
##
|
|
449
|
+
## 8. 权限控制
|
|
260
450
|
|
|
261
451
|
| type | 参数 | 说明 |
|
|
262
452
|
|---|---|---|
|
|
@@ -271,7 +461,7 @@ func connectAuthenticated(publicURL: URL, token: String) -> URLSessionWebSocketT
|
|
|
271
461
|
|
|
272
462
|
---
|
|
273
463
|
|
|
274
|
-
##
|
|
464
|
+
## 9. 新会话默认配置
|
|
275
465
|
|
|
276
466
|
| type | 参数 | 说明 |
|
|
277
467
|
|---|---|---|
|
|
@@ -289,7 +479,7 @@ func connectAuthenticated(publicURL: URL, token: String) -> URLSessionWebSocketT
|
|
|
289
479
|
|
|
290
480
|
---
|
|
291
481
|
|
|
292
|
-
##
|
|
482
|
+
## 10. 分支(fork)
|
|
293
483
|
|
|
294
484
|
```json
|
|
295
485
|
{ "type": "fork", "sessionId": "session-abc", "atSeq": 42 }
|
|
@@ -300,7 +490,7 @@ func connectAuthenticated(publicURL: URL, token: String) -> URLSessionWebSocketT
|
|
|
300
490
|
|
|
301
491
|
---
|
|
302
492
|
|
|
303
|
-
##
|
|
493
|
+
## 11. 宿主信息
|
|
304
494
|
|
|
305
495
|
| type | 返回 |
|
|
306
496
|
|---|---|
|
|
@@ -314,13 +504,14 @@ func connectAuthenticated(publicURL: URL, token: String) -> URLSessionWebSocketT
|
|
|
314
504
|
|
|
315
505
|
---
|
|
316
506
|
|
|
317
|
-
##
|
|
507
|
+
## 12. 服务端主动推送
|
|
318
508
|
|
|
319
509
|
| kind | 触发时机 |
|
|
320
510
|
|---|---|
|
|
321
511
|
| `paired` | 首次配对成功;仅此一次返回长期设备 token |
|
|
322
|
-
| `hello` | 连接成功:`{ "kind":"hello", "protocol":
|
|
512
|
+
| `hello` | 连接成功:`{ "kind":"hello", "protocol":3, "capabilities":["images"], "authenticated":true, "port":3080, "clients":1 }` |
|
|
323
513
|
| `event` | 任意会话的 agent 输出(见下) |
|
|
514
|
+
| `question-requested` / `question-resolved` | Human-in-the-loop 问题请求与最终状态 |
|
|
324
515
|
| `pong` / `subscribed` / `sent` | 对应请求的回复 |
|
|
325
516
|
|
|
326
517
|
### `event` 帧(agent 实时输出)
|
|
@@ -329,7 +520,7 @@ func connectAuthenticated(publicURL: URL, token: String) -> URLSessionWebSocketT
|
|
|
329
520
|
"event": { "type": "assistant/chunk", "turn": 1, "step": 0, "chunkType": "text-delta", "text": "正在" } }
|
|
330
521
|
```
|
|
331
522
|
`event.type` 覆盖(精炼字段):
|
|
332
|
-
- `user/message` → `{text, source}`
|
|
523
|
+
- `user/message` → `{text, source, images?: ImageAttachmentRef[]}`
|
|
333
524
|
- `assistant/chunk` → `{turn, step, chunkType: text-delta|reasoning-delta|tool-call-delta|usage|finish, text?/tool?/usage?/finish?}`
|
|
334
525
|
- `assistant/message` → `{turn, step, text, reasoning, toolCalls[]}`
|
|
335
526
|
- `tool/call` → `{turn, step, callId, name, arguments}`
|
|
@@ -338,7 +529,7 @@ func connectAuthenticated(publicURL: URL, token: String) -> URLSessionWebSocketT
|
|
|
338
529
|
|
|
339
530
|
---
|
|
340
531
|
|
|
341
|
-
##
|
|
532
|
+
## 13. 端到端示例(Postman)
|
|
342
533
|
|
|
343
534
|
1. Connect → 收到 `hello`
|
|
344
535
|
2. `{"type":"sessions"}` → 挑 `sessionId`(或直接下一步自动建)
|
|
@@ -351,7 +542,7 @@ func connectAuthenticated(publicURL: URL, token: String) -> URLSessionWebSocketT
|
|
|
351
542
|
|
|
352
543
|
---
|
|
353
544
|
|
|
354
|
-
##
|
|
545
|
+
## 14. 安全注意
|
|
355
546
|
|
|
356
547
|
- `/ws/mobile` 的移动网关默认关闭;本机 WebUI 手动开启后,若 5 分钟内没有设备成功连接会自动关闭
|
|
357
548
|
- 网关开启后仍要求已配对设备凭证;不要把 `requireAuth` 设为 `false` 后暴露到网络
|
|
@@ -359,10 +550,11 @@ func connectAuthenticated(publicURL: URL, token: String) -> URLSessionWebSocketT
|
|
|
359
550
|
- DSH HTTP Server 本身没有 TLS、认证或 Origin policy;公网必须使用 TLS 反向代理和 `wss://`
|
|
360
551
|
- 长期 token 只保存在 iOS Keychain;服务端磁盘仅保存摘要
|
|
361
552
|
- `set-default` / `save-default-model` 是全局写操作,客户端 UI 应加确认
|
|
553
|
+
- `question-answer` / `question-cancel` 会直接恢复或终止等待中的 Agent 工具调用;只允许经过鉴权的可信设备提交,并按 `rpcId` 防止重复操作
|
|
362
554
|
|
|
363
555
|
---
|
|
364
556
|
|
|
365
|
-
##
|
|
557
|
+
## 15. 版本历史(插件)
|
|
366
558
|
|
|
367
559
|
| 版本 | 新增 |
|
|
368
560
|
|---|---|
|
|
@@ -380,6 +572,8 @@ func connectAuthenticated(publicURL: URL, token: String) -> URLSessionWebSocketT
|
|
|
380
572
|
| v0.1.16 | fork(新对话分支) |
|
|
381
573
|
| v0.1.17 | models 支持无 sessionId 全局目录;新增 providers |
|
|
382
574
|
| v0.3.0 | 默认设备鉴权;一次性二维码配对;摘要化凭证存储;WebUI 设备面板;在线状态和即时吊销 |
|
|
575
|
+
| v0.5.0 | Human-in-the-loop:转发 API Gateway question 请求、整批回答/取消、重连重放与多端状态收敛 |
|
|
576
|
+
| v0.6.0 | DSH 0.1.1 图片:WebSocket Base64 上传、实时图片引用、历史附件按会话安全读取 |
|
|
383
577
|
|
|
384
578
|
---
|
|
385
579
|
|
package/README.md
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/assets/whale-girl-ios-app-promo-16x9.png" alt="鲸鱼娘展示 DeepSeek Harness Mobile 与移动网关" width="100%">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
1
5
|
# dsh-plugin-mobile-gateway
|
|
2
6
|
|
|
3
|
-
为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 提供经过设备鉴权的持久化 WebSocket 网关,让 iOS
|
|
7
|
+
为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 提供经过设备鉴权的持久化 WebSocket 网关,让 iOS 等移动客户端能够查看工作区和历史会话、收发文字与图片、接收 Agent 实时输出、处理 Human-in-the-loop 选择,以及调整会话模型与权限。
|
|
4
8
|
|
|
5
9
|

|
|
6
10
|
|
|
@@ -11,20 +15,22 @@
|
|
|
11
15
|
|
|
12
16
|
## 直接安装(推荐,无需下载源码)
|
|
13
17
|
|
|
18
|
+
如果使用 nvm 安装 Node.js,并希望不打开 WebUI、直接从服务器终端生成 iOS 配对字符串,请参照[公网 IP 命令行配对指南](docs/public-ip-deployment-guide.md)。
|
|
19
|
+
|
|
14
20
|
### 1. 安装插件
|
|
15
21
|
|
|
16
22
|
确保本机已经安装并能正常运行 DeepSeek Harness,然后执行:
|
|
17
23
|
|
|
18
24
|
```bash
|
|
19
|
-
dsh plugin --profile web add dsh-plugin-mobile-gateway
|
|
25
|
+
dsh plugin --profile web add dsh-plugin-mobile-gateway@latest
|
|
20
26
|
```
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
这条命令会在执行时从 npm 获取 `latest` 标签指向的最新插件、安装依赖,并将插件加入 `web` profile 的 bundle 列表。用户不需要 clone 仓库,也不需要运行 `pnpm install`。已经安装的插件不会后台自动升级;重新安装最新版本后需要重启 WebUI。
|
|
23
29
|
|
|
24
30
|
如果希望固定版本,可以在包名后指定版本号:
|
|
25
31
|
|
|
26
32
|
```bash
|
|
27
|
-
dsh plugin --profile web add dsh-plugin-mobile-gateway@0.
|
|
33
|
+
dsh plugin --profile web add dsh-plugin-mobile-gateway@0.6.0
|
|
28
34
|
```
|
|
29
35
|
|
|
30
36
|
也可以不经过 npm,直接安装 GitHub 版本:
|
|
@@ -137,6 +143,7 @@ wss://gateway.example.com/ws/mobile
|
|
|
137
143
|
- 浏览工作区、未分组会话和服务端目录
|
|
138
144
|
- 创建工作区与新会话
|
|
139
145
|
- 加载历史消息与轨迹
|
|
146
|
+
- 从历史消息安全加载图片,并把 iOS 本地图片发送给 Agent
|
|
140
147
|
- 实时接收思考、工具调用和最终回答
|
|
141
148
|
- 向远端 Agent 发送任务
|
|
142
149
|
- 查询或切换会话模型、推理等级和访问权限
|
|
Binary file
|