构建
Wire 协议
Wire 主版本 1 · 9 个协商 Profile
Wire 主版本 1 是一份小型的失败即关闭契约:九个协商 Profile、20 种紧凑 JSON 消息、一个 LF 分隔符、受限字段,并且不携带内部 Agent 或账号标识。
1. Wire 规则
每个逻辑消息都是一个紧凑 JSON 的 UTF-8 对象,末尾恰好一个 LF 字节。完整帧连同 LF 最多 4096 字节。传输层可以拆分帧,但接收方绝不执行部分消息:只有完整对象通过校验后才改变状态。原始解码 API 从行解码器取得已移除分隔符的 JSON。接收方不接受来自不同逻辑消息的交错分片。接收方会在无效 UTF-8、超长输入或断连后清空当前拼帧状态。
九个协商 Profile 都使用同一个信封;可选 Profile 开始传输前必须先通过 Device Info 门控。
| 名称 | 类型 | 说明 |
|---|---|---|
v必填 | integer | 规范的无符号十进制整数 1;小数与指数写法无效。 |
id | ASCII string | present、answer、resolved 必填,error 的 id 可选,status 不带 id。出现时必须是 1–64 个字符,并匹配 [A-Za-z0-9._:-]+。 |
enums必填 | lowercase | 只能使用区分大小写的小写值。 |
unknown fields | bounded JSON | 有效且受限的未知可选字段会被忽略;未知值最多八层嵌套,字段名在帧上限内没有独立长度限制。 |
invalid input必填 | 失败即关闭 | 符合规范的解码器会拒绝坏 UTF-8 和任何嵌套层级的重复已知 key,包括等价 JSON 转义;未知类型、缺字段和越界值也会被拒绝。 |
Device Info 契约
Device Info 是从其 GATT characteristic 读取的紧凑 UTF-8 JSON 值,不使用换行拼帧。下面八个基础字段全部必填:wire 必须包含 1,profiles 必须包含 approval/1。model 与 fw都必须是非空可打印字符串,最多 64 字节 UTF-8。主机忽略未知 capability 字段。状态能力要求 profiles 同时包含status/1,并且 statusSlots >= 1。缺少statusSlots 或值为零,都表示不能发送状态。
| 名称 | 类型 | 说明 |
|---|---|---|
protocol / spec必填 | string | nexting-device 与非空规范版本;官方 App 按协商结果应用边界。 |
wire / profiles必填 | arrays | wire 包含 1;profiles 包含 approval/1。 |
model / fw必填 | string | 每项都非空可打印,最多 64 字节 UTF-8。 |
max_message_bytes必填 | integer | 至少 512;官方实现上限为 4096。 |
max_summary_bytes必填 | integer | 官方 App 接受由 Device Info 协商出的有界值。 |
statusSlots | integer? | 可选 0–8 能力;只有正数与 status/1 同时出现才启用。 |
2. present
主机下发一个合格审批,并启动相对单调寿命。新的 present 会替换当前请求;主机必须先用 replaced 完成旧请求,再下发新请求。
| 名称 | 类型 | 说明 |
|---|---|---|
v必填 | integer | 规范 Wire 主版本 1。 |
t必填 | string | 精确为 present。 |
id必填 | string | 当前请求 ID。 |
sum必填 | string | 0–240 字节 UTF-8。 |
opt必填 | tuple | 必须按顺序精确为 ["allow","deny"]。 |
ttl必填 | integer | 从接收时起相对 1–300000 ms。 |
{"v":1,"t":"present","id":"r1","sum":"Allow git push?","opt":["allow","deny"],"ttl":30000}
设备进入 Pending。带第三个选项、持久授权、多选行为或选项语义不明确的 prompt 必须留在手机上。
3. answer
设备只能回答当前可见请求,随后进入 Waiting Resolution。如果 1000 ms 后仍未完成,可重发完全相同的 answer,直到收到 resolution 或本地 TTL 过期;绝不能改变选择。
| 名称 | 类型 | 说明 |
|---|---|---|
v必填 | integer | 规范 Wire 主版本 1。 |
t必填 | string | 精确为 answer。 |
id必填 | string | 必须匹配可见请求。 |
ch必填 | enum | allow 或 deny。 |
{"v":1,"t":"answer","id":"r1","ch":"allow"}
主机按请求 ID 去重,锁定第一个硬件选择,并且同一时间最多运行一次权威 action sink 尝试。
4. resolved
无论设备处于 Pending 还是 Waiting Resolution,匹配的 resolved 都会立即清除可见审批 UI 与缓存的重试状态。权威 sink 接受时原因是 answered,寿命结束时是 expired,主机撤回时是 cancelled,更新请求取代旧请求时是 replaced。
| 名称 | 类型 | 说明 |
|---|---|---|
v必填 | integer | 规范 Wire 主版本 1。 |
t必填 | string | 精确为 resolved。 |
id必填 | string | 要完成的请求。 |
r必填 | enum | answered、expired、cancelled 或 replaced。 |
{"v":1,"t":"resolved","id":"r1","r":"replaced"}
迟到或未知的 resolution 不能清除另一个当前请求。
5. error
Error 只用于诊断,并且遵循失败即关闭。error 的 id 可选;code 必填,可取 bad_message、message_too_large、unsupported_version、 unsupported_profile、unknown_request、not_authorized 或 busy。如果回复会放大流量或泄露授权状态,实现可以静默丢弃畸形或攻击者控制的输入。
| 名称 | 类型 | 说明 |
|---|---|---|
v必填 | integer | 规范 Wire 主版本 1。 |
t必填 | string | 精确为 error。 |
id | string? | 可选的相关请求 ID。 |
code必填 | enum | 必填的协议错误码。 |
{"v":1,"t":"error","id":"r1","code":"unknown_request"}
收到 error 不代表被拒绝的消息曾执行过任何部分。
6. status
Status 是易失的全量替换。每个有效帧原子替换全部已跟踪槽;agents: [] 清空所有槽。Agent state 可取 idle、thinking、working、complete、needs_input 或 error。畸形帧保留之前的状态,断连或重启会清除已渲染状态。
| 名称 | 类型 | 说明 |
|---|---|---|
v必填 | integer | 规范 Wire 主版本 1。 |
t必填 | string | 精确为 status。 |
agents必填 | array | 0–8 项;空数组清空全部槽。 |
slot必填 | integer | 帧内唯一的 0–7。 |
state必填 | enum | 必填的状态枚举。 |
label | string? | 可选 1–64 字节 UTF-8,不含控制字符。 |
{"v":1,"t":"status","agents":[{"slot":0,"state":"thinking","label":"fix login bug"}]}
设备渲染自己支持的最低编号实体槽,并忽略更高槽。status 永远不会创建、回答、过期或完成审批。
交互 Profile
以下已冻结 Profile 共用相同拼帧与失败即关闭规则。Device Info 必须先声明精确 Profile,Host 才会收发对应消息。
| 名称 | 类型 | 说明 |
|---|---|---|
navigation/1 | 双向 | nav_present、nav_move、nav_select、nav_resolved |
keys/1 | 双向 | keymap 与带重放保护的 key_event |
rotary/1 | 双向 | rotary_map、rotary_event、rotary_press |
voice/1 | 双向 | voice_event 与 voice_state;只传控制,不传音频或转写 |
text/1 | 下行 | 发往已声明屏幕通道的有界纯文本 |
usage/1 | 下行 | usage 与 usage_clear,用于信息展示 |
config/1 | 双向 | 原子版本化 config 与 config_result |
{"v":1,"t":"key_event","slot":3,"event":"press","seq":18}
{"v":1,"t":"rotary_event","slot":0,"delta":1,"seq":19}
{"v":1,"t":"voice_event","event":"start","seq":20}7. 测试向量
在导出的源码根目录运行可执行 JavaScript 参考测试。它会读取九个 Profile 的共享向量,并覆盖编解码、拼帧、relay 竞争、序号和拒绝案例。
node --test reference/js/test/*.test.mjsprotocol/vectors/ 下的全部文件当作规范行为,而不是示意样例。