5.3 KiB
5.3 KiB
DSHWatch 通信协议细化(v0.1)
本文件是 docs/DESIGN.md §4 的细化,定义三段链路的字段级协议,便于后续按此写代码。仍属"对齐粒度"文档。
0. 概述
三跳:
- 手表(DSHWatch) <-> 手机(DSHWatchPhone):BLE / @system.interconnect / Wearable MessageApi。
- 手机 <-> 服务器(DSHWatch_Server):WebSocket。
- 服务器 <-> DSH 插件(dsh-watch-bridge):WebSocket。
手机在 1 和 2 之间做双向网关翻译。手表看到的永远是"整理过的短文本",不直接透传原始大 JSON。
1. 手表 <-> 手机(BLE / interconnect)
1.1 物理通道
-
手表:@system.interconnect 的 connect.send({ data: obj })。
-
手机:小米穿戴 SDK MessageApi.sendMessage(nodeId, byte[])。
-
大数据(文本 > 约 1KB):按 base64 分片。官方 demo 用 header/data/end 三段;本项目沿用:
开始帧:{ "type":"header", "totalChunks": N, "id":"..." } 数据帧:{ "type":"data", "id":"...", "index": i, "chunk":"base64..." } 结束帧:{ "type":"end", "id":"..." }
1.2 消息信封
{
"to": "phone" | "watch",
"type": "hello" | "status" | "event" | "command" | "text" | "cancel" | "settings" | "ack" | "heartbeat",
"seq": 123,
"ts": 1760000000000,
"payload": { ... }
}
1.3 type 与 payload
- hello:{ "role":"watch|phone", "version":"0.1" }
- status:{ "online":bool, "harnessOnline":bool, "running":bool, "sessionId":"...", "sessionTitle":"...", "model":"...", "server":"..." }
- event:{ "kind":"session/event|session/projection", "sessionId":"...", "summary":"...", "seq":123 }
- command:{ "kind":"prompt|quick|select_session", "sessionId":"...", "content":"...", "quick":"..." }
- text:{ "sessionId":"...", "text":"..." }
- cancel:{ "sessionId":"..." }
- settings:{ "serverUrl":"...", "userId":"...", "token":"..." }
- ack:{ "ok":bool, "error":{ "code":"...", "message":"..." } }
- heartbeat:{}
1.4 顺序与可靠性
- seq 手表端单调递增。
- 手机端收到后原样回 ack(ok)。
- 分片用 id 关联;缺失分片则丢弃该条并 ack 失败。
2. 手机 <-> 服务器(WebSocket)
连接地址:ws(s)://host:port/ws(或 /)。
2.1 握手
手机发送:
{ "type":"hello", "role":"client", "userId":"...", "token":"..." }
服务器回:
{ "type":"hello", "role":"client", "online":true, "harness":true, "status":{...} }
2.2 请求/响应
手机 -> 服务器:
{ "type":"request", "requestId":"uuid", "method":"session.prompt", "timeoutMs":30000, "params":{...} }
服务器 -> 手机(转发给 Harness 后回):
{ "type":"response", "requestId":"uuid", "ok":true, "result":{...} }
{ "type":"response", "requestId":"uuid", "ok":false, "error":{ "code":"...", "message":"..." } }
2.3 推送(服务器 -> 手机)
{ "type":"harness", "online":true }
{ "type":"status", "status":{...} }
{ "type":"event", "event":{ "type":"session/event", "sessionId":"...", "event":{...} } }
{ "type":"notice", "code":"...", "message":"..." }
2.4 method 白名单(插件透传)
- harness.status / harness.prompt
- session.list/create/history/prompt/cancel/rename/fork/models/selectModel/attachment
- goal.create/edit/pause/resume/complete/clear
- subagent.list/history/prompt/interrupt
- workspace.list/create/rename/delete/insertBefore/insertSessionBefore/archiveSession
- host.describe/pickDirectory/listDirectory/createDirectory/openPath
- skill.list
- agentPreset.list/select/read/copy/openDocument/remove
- llm.providers/models/discoverModels
- settings.describe/openDocument/update/replace/mutate
- credentials.describe/set/unset
3. 服务器 <-> DSH 插件(WebSocket)
3.1 握手
插件 -> 服务器:
{ "type":"hello", "role":"dsh", "userId":"...", "token":"..." }
服务器回:
{ "type":"hello", "role":"dsh", "online":true, "status":{ "userId":"..." } }
3.2 转发
服务器 -> 插件(来自手机):
{ "type":"request", "requestId":"uuid", "method":"...", "params":{...} }
插件 -> 服务器:
{ "type":"response", "requestId":"uuid", "ok":true, "result":{...} }
{ "type":"response", "requestId":"uuid", "ok":false, "error":{...} }
3.3 推送
插件 -> 服务器:
{ "type":"status", "status":{...} }
{ "type":"event", "event":{...} }
{ "type":"ping" } # 心跳
3.4 状态快照形态
{
"online": true,
"ts": 1760000000000,
"version": "0.0.1",
"cwd": "D:/...",
"provider": "deepseek",
"model": "deepseek-chat",
"attachedSessions": 2,
"activeSessionId": "session-xxx",
"running": false,
"sessions": [ { "sessionId":"...", "updatedAt":123, "running":false, "blank":false, "agentPreset":"..." } ]
}
4. 错误码约定
auth-error / missing-userId / bad-request / harness-offline / harness-timeout / invoke-error / duplicate-requestId / invalid-JSON / unknown-path
5. 备注
- 服务器不落盘,纯内存转发。
- token 建议走 wss,且在服务端用环境变量或本地 config.json,不入 git。
- 客户端重连:指数退避(1s,2s,4s,8s,上限 30s)。
- 事件流可能很大,手表端只渲染 kind=session/event 且是 text 的摘要;其余在手机端可看原始。