dsh-plugin-mobile-gateway 0.10.2 → 0.10.3
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 +5 -2
- package/README.md +13 -3
- package/lib/dsh-host-adapter.mjs +19 -13
- package/lib/index.mjs +11 -2
- package/package.json +2 -2
package/PROTOCOL.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# dsh Mobile Gateway — WebSocket 协议参考
|
|
2
2
|
|
|
3
|
-
移动端通过经过设备鉴权的 WebSocket 连接与 dsh 通信:订阅 agent 实时输出、发送文字和图片、处理 Human-in-the-loop 提问与操作审批、查询会话/工作区/历史、调整会话配置。本协议由持久化插件 `dsh-plugin-mobile-gateway` 实现。当前源码适配 DSH 0.2.
|
|
3
|
+
移动端通过经过设备鉴权的 WebSocket 连接与 dsh 通信:订阅 agent 实时输出、发送文字和图片、处理 Human-in-the-loop 提问与操作审批、查询会话/工作区/历史、调整会话配置。本协议由持久化插件 `dsh-plugin-mobile-gateway` 实现。当前源码适配 DSH 0.2.1-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
|
+
|
|
5
|
+
DSH Remote 要求的 `peer` 由网关在 Host 内部创建并注入,身份沿用已配对设备的本机用户权限,生命周期随插件释放;移动端请求帧不增加 `peer` 字段,`dsh-mobile-v1` 协议不变。
|
|
4
6
|
|
|
5
7
|
- **本机端点**:`ws://127.0.0.1:3080/ws/mobile`(与 dsh web GUI 同端口)
|
|
6
8
|
- **局域网端点**:`ws://<电脑的私有局域网 IP>:3081/ws/mobile`(插件独立监听,只提供经过鉴权的 WebSocket)
|
|
@@ -1175,7 +1177,8 @@ DSH Web 的“深度求索中,用时 22 秒 ···”是客户端文案,不
|
|
|
1175
1177
|
|
|
1176
1178
|
| 版本 | 新增 |
|
|
1177
1179
|
|---|---|
|
|
1178
|
-
| v0.10.
|
|
1180
|
+
| v0.10.3(当前源码) | 适配 DSH 0.2.1-rc.2;为 Remote 请求及实时订阅补齐 Connection Peer,修复读取 `signal` 时的空值报错,并随网关停止释放 Peer;移动端协议保持不变 |
|
|
1181
|
+
| v0.10.2 | 补齐 `deliverables/presented` 文件卡片实时字段;新增 `file-references-v1` 能力与 `file-resolve` 安全文件引用解析;复用现有分块下载支持移动端预览接入 |
|
|
1179
1182
|
| v0.10.1 | 新增官方 speech Remote 语音输入桥接;模型准备与状态同步;移动端音频转文字与取消转写;连接关闭时清理语音任务 |
|
|
1180
1183
|
| v0.10.0 | 响应式居中控制面板与主题同步;自定义下拉菜单;独立配对模态窗口与缩放动画;按运行实例关闭重启提醒;在线设备折叠列表与移动端设备名称上报;Tunnel 配置交互优化 |
|
|
1181
1184
|
| v0.9.0 | DSH 0.2.0-rc.2 插件管理;PC 端 Cloudflare Quick / 命名 Tunnel 及独立重启;插件私有 `cloudflared` 缓存及校验;配对入口选择指引;网关重启与 DSH Web 停止工具 |
|
package/README.md
CHANGED
|
@@ -6,11 +6,13 @@
|
|
|
6
6
|
|
|
7
7
|
DeepSeek Harness 的设备鉴权移动网关,支持会话与实时事件、排队消息同步及编辑/删除/Steer、Session 归档和重命名的双向同步、停止当前生成并稍后继续、任务列表和当前 Goal 同步及管理、服务端驱动的命令和技能菜单、Human-in-the-loop、图片及文件传输。安装后,Harness WebUI 左侧边栏会出现“移动设备”入口,可直接开启网关、生成配对二维码和管理可信设备。
|
|
8
8
|
|
|
9
|
-
> 当前源码以 **DSH 0.2.
|
|
9
|
+
> 当前源码以 **DSH 0.2.1-rc.2** 为适配基线,需要 Connection Peer 接口,使用 Session format 4;旧版历史游标需重新取得基线。
|
|
10
10
|
>
|
|
11
11
|
> 实时流已改为独立 `assistant-stream` 帧,移动端需要按 [rc.2 接入说明](docs/dsh-rc2-mobile-integration.md) 更新订阅、缓存与分页处理。当前修改尚未发布。
|
|
12
12
|
>
|
|
13
|
-
> v0.10.2
|
|
13
|
+
> v0.10.3(当前源码):适配 DSH 0.2.1-rc.2,为 Remote 请求及实时订阅补齐 Connection Peer,修复 `Cannot read properties of undefined (reading 'signal')` 报错,并在网关停止时释放 Peer。移动端协议保持不变。
|
|
14
|
+
>
|
|
15
|
+
> v0.10.2:新增文件卡片事件透传与安全文件引用解析,复用现有分块下载,为移动端文件卡片和正文文件链接预览提供协议支持。App 接入方式见 [文件卡片与预览接入说明](docs/file-cards-mobile-integration.md)。
|
|
14
16
|
>
|
|
15
17
|
> v0.10.1:新增官方语音输入接入,支持语音模型准备与状态同步、移动端音频转文字、取消转写及连接关闭后的任务清理。通过官方 speech Remote 处理识别,移动网关无需打包 ASR 模型。
|
|
16
18
|
>
|
|
@@ -24,7 +26,15 @@ DeepSeek Harness 的设备鉴权移动网关,支持会话与实时事件、排
|
|
|
24
26
|
|
|
25
27
|
## 协议与 DSH 兼容层
|
|
26
28
|
|
|
27
|
-
移动端连接的是本项目维护的 `dsh-mobile-v1`,不是 DSH 的内部 Remote 协议。插件内部通过独立 Host Adapter 对接 DSH 0.2.
|
|
29
|
+
移动端连接的是本项目维护的 `dsh-mobile-v1`,不是 DSH 的内部 Remote 协议。插件内部通过独立 Host Adapter 对接 DSH 0.2.1-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)。
|
|
30
|
+
|
|
31
|
+
DSH 0.2.1-rc.2 要求每次 Remote 调用携带 `peer`。网关通过 `ctx.connection.createPeer()` 创建一个随插件释放的本机用户 Peer,普通请求、首帧读取和实时订阅都由 Host Adapter 注入它;移动端不能设置调用方身份。问答与审批继续由网关的 Host 监听器处理。旧网关漏传此字段时会出现 `Cannot read properties of undefined (reading 'signal')`,升级 DSH 后需重新安装本源码版本并重启 `dsh web`,手机无需更改协议。
|
|
32
|
+
|
|
33
|
+
可用本机已安装的官方 DSH 验证此契约(真实 Gateway / Connection,业务服务使用测试数据,不读写用户会话):
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
DSH_INSTALL_ROOT=/absolute/path/to/@deepseek-ai/dsh node test/host-official.test.mjs
|
|
37
|
+
```
|
|
28
38
|
|
|
29
39
|
配对鉴权和 `dsh-mobile-v1` / `hello.protocol = 3` 保持不变。新版实时 token 不占用持久事件的 `seq`:客户端显式订阅 `assistantStream: true`,接收原子的 `session-snapshot` 和独立增量;普通 `event` 只携带持久事件。未接入新订阅的客户端只能收到持久消息。
|
|
30
40
|
|
package/lib/dsh-host-adapter.mjs
CHANGED
|
@@ -13,7 +13,7 @@ function requireArray(value, endpoint) {
|
|
|
13
13
|
return value
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
export const DSH_VERSION = '0.2.
|
|
16
|
+
export const DSH_VERSION = '0.2.1-rc.2'
|
|
17
17
|
export const SESSION_FORMAT_VERSION = 4
|
|
18
18
|
|
|
19
19
|
export function readHistoryRecords(records) {
|
|
@@ -44,12 +44,12 @@ export function readSessionSnapshot(frame, sessionId) {
|
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
async function firstStreamFrame(
|
|
47
|
+
async function firstStreamFrame(stream, namespace, method, args, signal) {
|
|
48
48
|
const ownedAbort = new AbortController()
|
|
49
49
|
const streamSignal = signal ? AbortSignal.any([signal, ownedAbort.signal]) : ownedAbort.signal
|
|
50
50
|
let iterator
|
|
51
51
|
try {
|
|
52
|
-
const iterable = await
|
|
52
|
+
const iterable = await stream(namespace, method, args, streamSignal)
|
|
53
53
|
const iteratorFactory = iterable?.[Symbol.asyncIterator] ?? iterable?.[Symbol.iterator]
|
|
54
54
|
if (typeof iteratorFactory !== 'function') throw new Error(`${namespace}/${method} returned an invalid stream`)
|
|
55
55
|
iterator = iteratorFactory.call(iterable)
|
|
@@ -78,21 +78,28 @@ function sessionListArgs(request) {
|
|
|
78
78
|
* descriptors. Its public methods intentionally match mobile-gateway domain
|
|
79
79
|
* operations, not the upstream transport envelope.
|
|
80
80
|
*/
|
|
81
|
-
export function createDshHostAdapter(typertGateway) {
|
|
81
|
+
export function createDshHostAdapter(typertGateway, peer) {
|
|
82
82
|
if (!typertGateway || typeof typertGateway.invoke !== 'function' || typeof typertGateway.stream !== 'function') {
|
|
83
83
|
throw new Error('mobile-gateway requires the DSH Remote Gateway host service')
|
|
84
84
|
}
|
|
85
|
+
if (!peer || !(peer.signal instanceof AbortSignal)) {
|
|
86
|
+
throw new Error('mobile-gateway requires a DSH Connection Peer (DSH 0.2.1-rc.2 or later)')
|
|
87
|
+
}
|
|
85
88
|
|
|
86
89
|
const invoke = (namespace, method, args, signal) => typertGateway.invoke({
|
|
87
90
|
namespace,
|
|
88
91
|
method,
|
|
89
92
|
args,
|
|
93
|
+
peer,
|
|
90
94
|
...(signal === undefined ? {} : { signal }),
|
|
91
95
|
})
|
|
96
|
+
const stream = (namespace, method, args, signal) => typertGateway.stream({
|
|
97
|
+
namespace, method, args, peer, signal,
|
|
98
|
+
})
|
|
92
99
|
|
|
93
100
|
const sessionSnapshot = async (request, signal) => {
|
|
94
101
|
const frame = requireRecord(await firstStreamFrame(
|
|
95
|
-
|
|
102
|
+
stream,
|
|
96
103
|
'session',
|
|
97
104
|
'follow',
|
|
98
105
|
requestArgs(request),
|
|
@@ -200,7 +207,7 @@ export function createDshHostAdapter(typertGateway) {
|
|
|
200
207
|
return {
|
|
201
208
|
speech: {
|
|
202
209
|
catalog: (signal) => invoke('speech', 'catalog', {}, signal),
|
|
203
|
-
follow: (signal) =>
|
|
210
|
+
follow: (signal) => stream('speech', 'follow', {}, signal),
|
|
204
211
|
configure: (patch, signal) => invoke('speech', 'configure', { patch }, signal),
|
|
205
212
|
prepare: (providerId, options, signal) => invoke('speech', 'prepare', {
|
|
206
213
|
providerId, ...(options === undefined ? {} : { options }),
|
|
@@ -248,7 +255,7 @@ export function createDshHostAdapter(typertGateway) {
|
|
|
248
255
|
},
|
|
249
256
|
workspace: {
|
|
250
257
|
async list(_payload = {}, signal) {
|
|
251
|
-
const frame = requireRecord(await firstStreamFrame(
|
|
258
|
+
const frame = requireRecord(await firstStreamFrame(stream, 'workspace', 'follow', {}, signal), 'workspace/follow')
|
|
252
259
|
if (frame.type !== 'baseline') throw new Error('workspace/follow returned an invalid opening baseline')
|
|
253
260
|
return requireRecord(frame.value, 'workspace/follow baseline')
|
|
254
261
|
},
|
|
@@ -371,18 +378,17 @@ export function createDshHostAdapter(typertGateway) {
|
|
|
371
378
|
if (event.type === 'turn/start') admittedPrompts.delete(sessionId)
|
|
372
379
|
},
|
|
373
380
|
openSessionStream(sessionId, signal) {
|
|
374
|
-
return
|
|
375
|
-
namespace: 'session', method: 'follow',
|
|
381
|
+
return stream('session', 'follow',
|
|
376
382
|
// Bound the opening window at the Host; older records remain available via session.page.
|
|
377
|
-
|
|
383
|
+
requestArgs({ address: { kind: 'session', sessionId }, maxMessages: 12, assistantStream: true }),
|
|
378
384
|
signal,
|
|
379
|
-
|
|
385
|
+
)
|
|
380
386
|
},
|
|
381
387
|
openControlStream(signal) {
|
|
382
|
-
return
|
|
388
|
+
return stream('session', 'control', {}, signal)
|
|
383
389
|
},
|
|
384
390
|
openWorkspaceStream(signal) {
|
|
385
|
-
return
|
|
391
|
+
return stream('workspace', 'follow', {}, signal)
|
|
386
392
|
},
|
|
387
393
|
}
|
|
388
394
|
}
|
package/lib/index.mjs
CHANGED
|
@@ -2193,13 +2193,19 @@ const plugin = {
|
|
|
2193
2193
|
// Hard dependencies: the gateway registers on the host web server and
|
|
2194
2194
|
// admits mobile messages through the Host Remote Gateway. Cordis must not
|
|
2195
2195
|
// activate this row before either service exists.
|
|
2196
|
-
inject: ['webServer', 'typertGateway', 'agentDefaultModel'],
|
|
2196
|
+
inject: ['webServer', 'typertGateway', 'connection', 'agentDefaultModel'],
|
|
2197
2197
|
// exported for local tests; Cordis ignores unknown plugin fields
|
|
2198
2198
|
admitMessage,
|
|
2199
2199
|
handleQuery,
|
|
2200
2200
|
apply(ctx, config) {
|
|
2201
2201
|
const webServer = ctx.webServer
|
|
2202
|
-
|
|
2202
|
+
if (typeof ctx.connection?.createPeer !== 'function') {
|
|
2203
|
+
throw new Error('mobile-gateway requires DSH 0.2.1-rc.2 or later with Connection.createPeer')
|
|
2204
|
+
}
|
|
2205
|
+
// 已配对设备沿用本机用户权限;身份只由 Host 创建,不能由移动端参数指定。
|
|
2206
|
+
// 问答与审批仍由本插件的 Host 监听器处理,无需另开 Remote 交互流。
|
|
2207
|
+
const peer = ctx.connection.createPeer({ principal: { kind: 'owner' }, interactions: 'none' })
|
|
2208
|
+
const api = createDshHostAdapter(ctx.typertGateway, peer)
|
|
2203
2209
|
const host = api
|
|
2204
2210
|
const agentDefaultModel = ctx.agentDefaultModel
|
|
2205
2211
|
const options = {
|
|
@@ -3471,6 +3477,9 @@ const plugin = {
|
|
|
3471
3477
|
if (waitTimer) clearTimeout(waitTimer)
|
|
3472
3478
|
clearInterval(fileTransferExpiryTimer)
|
|
3473
3479
|
backgroundStreamAbort.abort()
|
|
3480
|
+
peer.dispose().catch((error) => {
|
|
3481
|
+
log(`Host peer disposal failed: ${error && error.message ? error.message : String(error)}`)
|
|
3482
|
+
})
|
|
3474
3483
|
speechInput.dispose()
|
|
3475
3484
|
for (const pending of [...pendingQuestions.values()]) fallbackQuestion(pending)
|
|
3476
3485
|
for (const pending of [...pendingApprovals.values()]) fallbackApproval(pending)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-mobile-gateway",
|
|
3
|
-
"version": "0.10.
|
|
4
|
-
"description": "DeepSeek Harness
|
|
3
|
+
"version": "0.10.3",
|
|
4
|
+
"description": "DeepSeek Harness 移动网关,支持安全设备配对、实时会话同步、插件管理、官方语音输入、文件卡片与安全文件预览、局域网及 Cloudflare Tunnel,以及自适应深浅色控制面板。0.10.3 适配 DSH 0.2.1-rc.2,为 Remote 请求及实时订阅补齐 Connection Peer,修复读取 signal 时的空值报错,并在网关停止时释放 Peer。",
|
|
5
5
|
"main": "lib/index.mjs",
|
|
6
6
|
"files": [
|
|
7
7
|
"bin",
|