跳转到内容

Device 离线投递与 Mailbox

Device Mailbox 让调用方在设备离线、重连或暂时不可路由时,把一次设备命令保存为 durable operation;设备恢复后主动拉取并执行。它适合构建、同步、采集等可以异步完成的结构化任务。

Mailbox 不是消息推送、FIFO 队列或 WebSocket 的替代品:网关不会通过 APNs/FCM 唤醒设备,也不会把所有实时调用失败自动改成入队。命令作者先声明交付能力,调用方再为每次调用显式选择策略。

~help 中每条设备命令的 delivery 是能力声明:

命令 delivery 含义
realtime 只允许当前 WebSocket 实时调用;也是缺省值
mailbox 只允许持久化入队
both 同时允许实时与 Mailbox

调用时的 --delivery 是本次调用策略:

调用策略 行为
realtime 只尝试实时调用;也是缺省值
mailbox 不尝试实时调用,直接创建 operation
fallback 对 both 命令先尝试实时调用;只有网关能确定“尚未派发”时才入队,避免重复执行副作用

fallback 不是“遇到任意 5xx 就再发一次”。如果调用可能已经发送但结果未知,网关会返回不可盲目重试的错误,不会再创建 operation。

1. 声明可离线投递的结构化命令

Section titled “1. 声明可离线投递的结构化命令”

CLI 的 Shell 与文件入口仍是实时能力。要使用 Mailbox,应通过 strict JSON command profile 暴露固定 executable 与 argv,并在命令上声明 mailbox 或 both:

device-ops.json
{
"version": 1,
"path": "ops",
"description": "构建机维护任务",
"commands": [
{
"name": "sync-repository",
"description": "同步一个已授权仓库",
"executable": "/usr/local/bin/sync-repository",
"argv": [
{
"input": "repository",
"flag": "--repository",
"required": true
}
],
"effect": "write",
"delivery": "both"
}
]
}

以前台进程运行:

Terminal window
tb connect \
--device-id build-01 \
--path device/build-01 \
--no-shell \
--command-profile ./device-ops.json

Linux 上也可以把同一 profile 交给 tb daemon install。CLI/daemon 只在暴露面确实包含 mailbox 或 both 命令时启用 Mailbox processor;首次连接和每次重连进入 ready 后各 drain 一次,不启动固定轮询 timer。

连接后先确认运行时契约:

Terminal window
tb help device/build-01/ops/sync-repository --json

检查返回命令的完整 path、arguments schema 和 delivery,不要只凭本页示例拼接路径。

设备在线时优先实时执行、明确未派发时再入队:

Terminal window
tb call device/build-01/ops/sync-repository \
--arg repository=acme/app \
--delivery fallback \
--ttl 900 \
--idempotency-key sync-acme-app-20260901

直接入队,不尝试实时调用:

Terminal window
tb call device/build-01/ops/sync-repository \
--arg repository=acme/app \
--delivery mailbox \
--ttl 900 \
--idempotency-key sync-acme-app-20260901

实际入队返回 HTTP 202;CLI 人类输出形如 queued dop_... (queued)。--ttl 是 operation 的总有效期,必须是正整数秒。--idempotency-key 是同一 caller owner 范围内的创建重试键:同一业务动作重试时复用它,不要为每次网络重试随机生成新值。

如果命令只声明 mailbox,选择 realtime 会被拒绝;如果命令只声明 realtime,选择 mailbox 或 fallback 也会被拒绝。

创建者 owner 的其他 SK,或对完整目标路径有 admin 的身份,可以管理 operation:

Terminal window
tb device op ls build-01
tb device op ls build-01 --state queued --state claimed
tb device op get build-01 <operation-id>
tb device op cancel build-01 <operation-id>

常见状态:

状态 含义
queued 已持久化,尚未由设备 claim
claimed 设备持有短 lease,可能正在执行
succeeded 设备返回成功结果
rejected 当前设备已不再暴露该命令,或 delivery 契约不再允许
failed handler 返回失败
result_unknown 执行可能已经开始,但设备重启等原因让终态无法恢复
cancelled 在 queued 阶段取消,handler 未由该 operation 启动
expired 超过总 TTL;若 executionMayHaveOccurred 为真,可能执行过

取消 queued operation 可以保证它尚未开始。取消 claimed operation 只是写入协作式取消请求;设备 handler 是否已经产生副作用仍需由业务幂等与补偿机制处理。

Presence 只说明实时 WebSocket 当前是否可路由,不决定 Mailbox 能力是否可见。当前身份有 read 时,离线设备及其命令仍保留在 ~tree 和 Dashboard 工作树中;命令面会根据 delivery 提供 realtime、mailbox 或 fallback 选择。

Dashboard 的 Device 页面还提供 Mailbox operation 列表、详情与取消入口。它与 tb device op 使用同一公开 HTTP 管理面,不会绕过 owner/admin 权限。

  • 标准 Node 把 operation 保存在 PostgreSQL 的 MailboxRepository,事务保护配额、幂等与状态迁移。自定义宿主必须显式注入领域仓库,普通 KV CAS 不是替代品。
  • arguments 与终态 result/error 使用从部署 encryption keyring 按独立域派生的 Mailbox 专用 AES-256-GCM key 静态加密。网关在 enqueue、claim、get、complete 时仍持有明文,因此这不是端到端加密。
  • 密文携带 keyId;新写使用 active key,旧根用于读取存量。通过密钥维护执行重加密与退役,不直接替换或删除根。
  • 单次 arguments 与终态结果各有 256 KiB JSON 上限。Mailbox 不承诺 FIFO;依赖顺序的业务应在命令自身建模。
  • operation 绑定创建时的设备凭证身份。禁用/吊销该 Device SK 会立即阻止继续 claim/complete;新 keyId 不会自动接管旧 operation。

标准 Node / Docker / Helm 使用同一 PG 领域仓库,沿已应用的清理调度收敛过期记录。备份应同时覆盖 PG、bootstrap keyring 与设备 installation journal。

@tool-bridge/sdk/device 提供 createDeviceMailboxProcessor,但它是 pull-only primitive:不会自行启动 timer、监听 WebSocket ready 或处理进程退出。宿主必须提供 installation-local durable journal,在初次 ready 与每次重连 ready 后调用 drain(),并在停止或 suspend 时 abort 当前 drain。

journal 的 put() 必须在 resolve 前真正持久化。进程在 handler 开始后崩溃时,processor 会保守收敛为 result_unknown,而不是再次执行可能有副作用的 handler。完整 SDK 边界见嵌入现有应用。

  • ~help 中只有明确设计为异步安全的命令声明 mailbox 或 both;
  • 设备离线时,受限调用身份仍能发现该命令并以 mailbox 入队;
  • 设备恢复到 ready 后 operation 从 queued 收敛到可解释终态;
  • fallback 在实时成功时不创建第二条 operation;
  • caller 只能看到自己的 operation,管理员接管需要完整目标路径的 admin;
  • queued 与 claimed 的取消语义、result_unknown 与可能执行过的 expired 已进入业务重试策略;
  • PostgreSQL、设备 installation journal 与受保护 keyring 都在备份/恢复与轮换计划内。