最近在想一个问题:能不能在手表上给服务器里的 AI Agent 派任务?比如出门前在手表上点一下「继续昨天那个重构」,路上看一眼任务进度,必要时点个「允许」或者「取消」。

结论是可以,但不要试图把 OpenBitFun 现有的 HarmonyOS 手表链路硬移植到 Zepp OS。更合适的路线是:

Zepp Device App
  -> Zepp Side Service
  -> 自建 HTTP Gateway
  -> Docker 化的 OpenBitFun CLI Host
  -> Agent Runtime / Workspace

这篇文章整理一套可以落地的技术方案:整体架构、Zepp 设置页、HTTP API、CLI Host 部署方式、Relay 的取舍、安全边界和 MVP 路线。

1. 目标与非目标

1.1 目标

第一版先做一个「轻量 Agent 控制器」,让 Zepp OS 手表可以:

  • 配置自己的服务端地址;

  • 完成设备配对;

  • 选择允许的工作区;

  • 提交一个文本任务;

  • 查看任务状态和增量输出;

  • 取消任务;

  • 在必要时处理远程权限审批。

真正执行任务的仍然是服务器上的 OpenBitFun CLI Host。手表不负责运行 Agent Runtime,也不保存模型 Provider 密钥。

1.2 非目标

第一版不做这些事情:

  • 不在手表上运行 Agent Runtime;

  • 不把桌面端完整 UI 搬到手表;

  • 不做复杂 Markdown / 文件树 / 图片消息;

  • 不追求服务端主动推送到手表;

  • 不自建 Relay;

  • 不改造 OpenBitFun 官方 Relay 协议。

这些都可以放到后续阶段,第一版先把「能派任务、能看进度、能取消」这条链路跑通。

2. 为什么不直接移植 HarmonyOS 手表方案

OpenBitFun 当前的 HarmonyOS 手表支持,核心依赖的是 HarmonyOS 的分布式能力:

  • 手表把 provisioning 请求写入 HarmonyOS Distributed KV;

  • 手机监听同一个 KV store;

  • 用户在手机上确认这块手表;

  • 手机向 Relay 注册手表设备;

  • 手机再用 X25519/AES-GCM 把 credential 密封给手表;

  • 手表拿到自己的 Relay credential 后,作为 controller 控制 Desktop/CLI Host。

这套设计对 HarmonyOS 很合理,但它依赖平台专属的分布式 KV、账号体系和加密交接流程。Zepp OS 没有这套能力。

Zepp OS 的官方架构也不相同。一个完整 Mini Program 通常包含:

  • Device App:跑在手表上,负责 UI 和传感器;

  • Side Service:跑在手机上的 Zepp App 里;

  • Settings App:可选,负责配置页面。

Device App 和 Side Service 之间通过 BLE / Messaging API 通信;Side Service 才能通过 Fetch API 访问外部 HTTP 服务。Zepp OS 官方架构文档对这个关系描述得很清楚,Fetch 能力见 Zepp OS Fetch API,BLE 通信见 Messaging API。

所以 Zepp OS 上更自然的链路不是「手表直接接入 Relay」,而是:

手表 Device App
  -> BLE
  -> 手机 Zepp App 中的 Side Service
  -> HTTPS
  -> 你的服务端

这也正好符合「主要通过 HTTP 服务与服务器对话」的目标。

3. 总体架构

flowchart LR subgraph Zepp["Zepp OS 生态"] Device["Device App<br/>手表 UI"] Settings["Settings App<br/>配置服务地址 / 配对信息"] Storage[("Settings Storage")] Side["Side Service<br/>运行在手机 Zepp App<br/>通过 fetch 访问 HTTPS"] end subgraph Edge["公网入口"] Proxy["Nginx / Caddy<br/>TLS 终止"] end subgraph Server["Linux Server / Docker Compose"] Gateway["zepp-gateway<br/>认证 · 限流 · API 适配 · 任务投影"] GatewayDB[("gateway.db<br/>device token hash / task cursor")] Cli["cli-host<br/>openbitfun daemon run"] Runtime["Agent Runtime<br/>Session / Turn / Tools / Permission"] Home[("openbitfun-home<br/>账号 session / device identity")] Workspaces[("/workspaces<br/>项目代码 / Git / 文件")] end Models["模型 Provider / MCP / 外部服务"] Relay["官方 Relay(可选)<br/>OpenBitFun 原生手机/桌面远控"] Device -- "BLE / Messaging API" --> Side Settings --> Storage Storage --> Side Side -- "HTTPS + device token" --> Proxy Proxy --> Gateway Gateway --> GatewayDB Gateway -- "本机调用<br/>openbitfun dispatch / CLI API" --> Cli Cli --> Runtime Runtime --> Workspaces Runtime --> Models Cli --- Home Relay -. "可选:OpenBitFun 原生远控" .-> Cli Side -- "状态 / 结果" --> Device flowchart TD subgraph Watch["Zepp OS 手表(控制端)"] DA["Device App<br/>服务状态 / 选择 workspace / 提交任务"] SS["Side Service(手机 Zepp App 里)<br/>读配置 · Fetch API"] SET["Settings App<br/>gateway_base_url 可自定义"] DA <-->|"BLE Messaging"| SS SET --> SS end SS -->|"1. HTTPS + device token"| NG["Nginx / Caddy<br/>TLS 终结 · 反代 127.0.0.1:8787"] subgraph Container["agent-host 容器"] GW["zepp-gateway<br/>认证/白名单/任务API/限流/审计/事件投影"] DISP["dispatch 适配<br/>幂等 · cursor 持久化 · single-flight"] CLI["OpenBitFun CLI"] RT["Agent Runtime"] GW --> DISP --> CLI --> RT end NG -->|"2. 反代"| GW RT --> WS[("工作区白名单 /workspaces/*")] GW --> DB[("SQLite gateway.db")] DAEMON["openbitfun daemon run(可选)"] -.-> RELAY[("官方 Relay")]

这张图里最重要的边界有三个:

  1. 手表端只做 UI 和轻量交互Device App 不直接访问外部服务器,它通过 BLE 把请求交给 Side Service。

  2. Zepp Gateway 是受信任的协议适配层它负责 HTTP 认证、限流、工作区白名单、任务幂等、事件 cursor,并把请求翻译成 OpenBitFun CLI 能理解的操作。

  3. CLI Host 才是真正的执行端
    Agent Runtime、工具调用、终端、文件读写、模型配置都在服务器上的 CLI Host 里。

官方 Relay 在这条 Zepp 链路里不是必需组件。它可以继续用于 OpenBitFun 原生手机、桌面和 CLI Host 的远程控制,但 Zepp 第一版不需要绕到 Relay。

4. Zepp 端设计:设置页必须支持自定义服务地址

Zepp 端建议拆成三个部分。

4.1 Device App

Device App 跑在手表上,只做轻量 UI:

  • 服务状态;

  • 工作区选择;

  • 常用任务入口;

  • 任务进度;

  • 取消按钮;

  • 权限确认按钮。

它不直接调用 Fetch API,而是通过 Messaging API 和 Side Service 通信。

4.2 Settings App

Settings App 是配置入口。除了常规设置外,服务地址必须由用户自己配置,否则这个方案只能服务单一部署。

建议提供这些配置项:

配置项

示例

说明

gateway_base_url

https://agent.example.com

Zepp Gateway 的服务地址

pairing_code

8F3K-2Q9A

首次配对码,不长期保存

default_workspace

openbitfun-main

默认工作区

poll_interval_ms

3000

手表活跃时的轮询间隔

show_debug_log

false

是否显示调试信息

配置写入 Zepp 的 Settings Storage,Side Service 监听这些配置变化。

服务地址建议做以下校验:

  • 去掉首尾空白和末尾 /;

  • 公网环境强制 https://;

  • 局域网调试允许 http://192.168.x.x 或 http://10.x.x.x;

  • 保存前调用一次 GET /v1/health;

  • 健康检查通过后,再进入配对流程。

4.3 Side Service

Side Service 是 Zepp 端真正的网络层:

  • 从 Settings Storage 读取服务地址;

  • 完成设备配对;

  • 保存 device token;

  • 调用 Gateway API;

  • 把任务状态通过 BLE 返回给 Device App;

  • 处理网络重试和错误提示。

它不应该保存模型 API Key,也不应该知道 CLI Host 的账号凭证。它只保存 Zepp Gateway 签发的 device token。

5. Zepp Gateway:新增的 HTTP 接入层

当前 OpenBitFun CLI Host 没有一个适合手表直接访问的 HTTP 对话 API,所以需要新增一个很小的 Gateway。

Gateway 的职责:

  • 设备配对与 token 签发;

  • HTTP 认证;

  • 请求限流;

  • 工作区白名单;

  • 任务提交幂等;

  • 调用本机 openbitfun dispatch;

  • 把 dispatch 状态转换成手表容易消费的小 JSON;

  • 保存任务 cursor;

  • 提供取消和远程权限审批入口。

Gateway 不应该做这些事:

  • 不重新实现 Agent Runtime;

  • 不保存模型 Provider API Key;

  • 不允许手表传任意文件路径;

  • 不转发原始终端输出;

  • 不把完整工作区暴露给手表。

5.1 技术选型

Gateway 可以用 Node.js/TypeScript + Fastify 做第一版,原因是:

  • 迭代快;

  • JSON API 实现简单;

  • 调用 CLI 子进程方便;

  • SQLite 持久化简单;

  • 后续要加 WebSocket/SSE/Long Polling 也方便。

如果希望和 OpenBitFun 主仓库保持同一技术栈,也可以用 Rust + Axum。第一版以快速验证 Zepp 端链路为主,TypeScript 会更轻。

5.2 持久化

Gateway 自己保留一个很小的 SQLite 数据库:

  • 设备记录;

  • token hash;

  • workspace 白名单;

  • task id 映射;

  • event cursor;

  • audit log。

真正的 Agent 状态和 workspace 仍然归 CLI Host 管理。

6. HTTP API 设计

第一版 API 不需要大,先保证闭环。

方法

路径

作用

GET

/v1/health

健康检查,用于设置页测试服务地址

POST

/v1/devices/pair

用 pairing code 换 device token

GET

/v1/workspaces

返回允许手表访问的工作区

POST

/v1/tasks

提交任务

GET

/v1/tasks/{task_id}

查询任务状态

GET

/v1/tasks/{task_id}/events

按 cursor 拉取增量事件

POST

/v1/tasks/{task_id}/cancel

取消任务

POST

/v1/permissions/{permission_id}/answer

回答远程权限请求

6.1 提交任务

POST /v1/tasks
Authorization: Bearer <device-token>
Content-Type: application/json

{
  "workspace_id": "openbitfun-main",
  "prompt": "继续检查昨天的 Docker 部署方案,并给出下一步",
  "client_request_id": "watch-01f8b7c2"
}

client_request_id 必须保留。手表网络不稳定,Side Service 可能重试,Gateway 要用它防止重复创建任务。

返回示例:

{
  "task_id": "task_01J8Z0M6",
  "status": "queued",
  "cursor": "0"
}

6.2 拉取任务事件

GET /v1/tasks/task_01J8Z0M6/events?cursor=183&wait_ms=20000
Authorization: Bearer <device-token>

返回示例:

{
  "task_id": "task_01J8Z0M6",
  "status": "running",
  "next_cursor": "219",
  "events": [
    {
      "cursor": "184",
      "type": "status",
      "text": "正在检查 Docker Compose 配置"
    },
    {
      "cursor": "185",
      "type": "tool",
      "text": "读取 docker-compose.yml"
    }
  ]
}

手表端第一版只渲染 text,不要把复杂工具事件直接搬到小屏幕上。

6.3 任务状态

建议使用一个很小的状态机:

queued -> running -> succeeded
                 -> failed
                 -> cancelled
                 -> waiting_permission -> running

远程权限审批可以复用 dispatch 的 remote approval policy。需要注意,AskUserQuestion 这类交互式问题不是 dispatch 的目标能力;如果任务需要用户提前选择,最好在 prompt 或 Gateway 参数里明确。

7. CLI Host 部署:建议使用 Docker

服务端 CLI Host 我建议用 Docker 部署,而不是直接安装在宿主机。

原因很简单:

  • Zepp Gateway 和 CLI Host 可以放同一个 Compose 项目;

  • CLI Host 不需要暴露公网端口;

  • 可以用 volume 限制工作区范围;

  • 升级和回滚更容易;

  • 容器里本来就应该用 openbitfun daemon run,而不是依赖 systemd 的 daemon install。

7.1 目标 Compose 形态

下面是一个目标形态。cli-host 部分可以先落地;zepp-gateway 是接下来需要实现的服务。

name: openbitfun-agent-host

services:
  cli-host:
    image: openbitfun-cli-host:local
    build:
      context: ./cli-host
    container_name: openbitfun-cli-host
    restart: unless-stopped
    environment:
      TZ: Asia/Shanghai
      HOME: /home/openbitfun
      OPENBITFUN_HOME: /home/openbitfun/.openbitfun
      RUST_LOG: info
    volumes:
      - openbitfun-home:/home/openbitfun/.openbitfun
      - /srv/workspaces:/workspaces
    tmpfs:
      - /tmp:size=512m,mode=1777
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    command: ["daemon", "run"]

  zepp-gateway:
    image: zepp-gateway:local
    build:
      context: ./zepp-gateway
    container_name: zepp-gateway
    restart: unless-stopped
    environment:
      NODE_ENV: production
      GATEWAY_LISTEN_ADDR: 0.0.0.0:8787
      GATEWAY_DB_PATH: /data/gateway.db
      OPENBITFUN_CLI_BIN: /opt/openbitfun/bin/openbitfun
    volumes:
      - gateway-data:/data
      - openbitfun-home:/home/openbitfun/.openbitfun:ro
    ports:
      - "127.0.0.1:8787:8787"
    depends_on:
      - cli-host

volumes:
  openbitfun-home:
  gateway-data:

外部只看到 Nginx/Caddy 暴露的 HTTPS,Gateway 只监听 127.0.0.1:8787,CLI Host 不暴露端口。

7.2 镜像工具链

如果只希望 Agent 读代码、改代码,一个薄镜像就够了。

如果希望 Agent 在服务器上跑构建和测试,镜像里还要放工具链。比如 Java/Vue 项目常见组合:

  • JDK;

  • Maven;

  • Node.js;

  • pnpm;

  • Python;

  • Git;

  • build-essential。

否则会出现「Agent 能改代码,但无法验证」的情况。

7.3 不建议的事情

  • 不把 / 挂进容器;

  • 不默认挂载 /var/run/docker.sock;

  • 不让 CLI Host 直接暴露到公网;

  • 不把手表 token、模型 Key、Relay credential 写进日志;

  • 不让手表直接传任意 shell 命令。

8. Relay 要不要部署?

对 Zepp 第一版来说:不需要部署 Relay。

因为 Zepp 的链路是:

Zepp Side Service -> HTTPS Gateway -> 本机 CLI Host

Gateway 和 CLI Host 在同一台服务器上,没必要经过官方 Relay。

官方 Relay 仍然有它的价值:

  • Desktop 远程控制;

  • 手机远程控制;

  • OpenBitFun 原生多设备路由;

  • 后续如果要把 Zepp 接入统一账号设备目录,可以再评估。

但不建议第一版就自建 Relay。自建 Relay 不只是启动一个容器,还涉及 HTTPS、身份认证、客户端 Relay URL、设备目录和对应客户端构建,会明显扩大项目范围。

9. 安全设计

Zepp Gateway 是这个方案里的信任边界,必须按公网服务来做。

风险

对策

服务地址被扫描

HTTPS、限流、IP allowlist、失败次数限制

device token 泄漏

数据库只存 hash;支持单设备撤销;token 不进入日志

重复提交任务

client_request_id 幂等

任意路径读写

workspace 白名单,不接收原始路径

Prompt 滥用

单设备速率限制、prompt 长度限制、审计日志

权限误确认

手表显示工具名和关键参数摘要,确认动作二次确认

容器逃逸影响宿主

非 root、cap_drop: ALL、只挂载 /workspaces、不挂 Docker socket

日志泄漏

不记录 token、模型 Key、完整敏感 prompt;必要内容做截断

配对流程建议这样做:

  1. 管理员在服务器生成一次性 pairing code;

  2. 用户在 Settings App 输入服务地址和 pairing code;

  3. Side Service 调用 /v1/devices/pair;

  4. Gateway 校验 pairing code;

  5. Gateway 返回长期 device token;

  6. Side Service 只保存 device token;

  7. pairing code 立即失效。

10. MVP 路线

Phase 0:Zepp 端 Spike

先验证 Zepp 基础链路:

  • Settings App 能保存服务地址;

  • Side Service 能读取配置;

  • Side Service 能通过 fetch 请求 /v1/health;

  • Device App 能通过 BLE 收到 Side Service 返回的结果。

这个阶段不接 OpenBitFun。

Phase 1:Gateway 最小闭环

实现:

  • /v1/health;

  • /v1/devices/pair;

  • /v1/workspaces;

  • /v1/tasks;

  • /v1/tasks/{id};

  • /v1/tasks/{id}/events。

后端先接 openbitfun dispatch,任务状态和事件 cursor 持久化。

Phase 2:手表端最小 UI

实现:

  • 服务状态页;

  • 工作区列表;

  • 常用 prompt 提交;

  • 任务状态页;

  • 取消按钮。

Phase 3:远程权限

实现:

  • waiting_permission 状态;

  • 权限摘要;

  • 允许 / 拒绝;

  • 超时和失败状态。

Phase 4:体验优化

后续再考虑:

  • 常用指令模板;

  • 手机 Settings App 编辑长 prompt;

  • 任务完成通知;

  • 更丰富的事件渲染;

  • 多工作区管理;

  • 与官方 Relay / 账号体系统一。

11. 关键风险

  1. Device App 不应被假设为可以直接访问公网标准路线仍然是 Device App -> BLE -> Side Service -> HTTPS。

  2. Side Service 生命周期不可控手表端不要依赖长连接。Gateway 必须保存任务状态和 cursor,让 Side Service 随时可以回来继续拉。

  3. CLI 镜像工具链决定 Agent 能不能验证代码如果服务器要跑 Maven、pnpm、pytest,这些工具要提前进镜像。

  4. Gateway 是明文边界Relay 的端到端加密不会覆盖这条 Zepp HTTP 链路,所以 Gateway 必须私有化部署并做好认证和审计。

  5. 第一版不要追求完整聊天
    dispatch 式任务最适合手表:提交、看进度、取消、拿结果。完整会话可以后面再补。

12. 总结

这套方案的核心不是「把 OpenBitFun 手表端移植到 Zepp OS」,而是给 Zepp OS 增加一个符合它平台架构的 HTTP 接入层:

Zepp Device App
  -> BLE
  -> Zepp Side Service
  -> HTTPS
  -> zepp-gateway
  -> Docker CLI Host
  -> Agent Runtime / Workspace

这样做有几个好处:

  • 不依赖 HarmonyOS 分布式 KV;

  • 不需要 Zepp 端实现复杂 Relay 协议;

  • 服务端可以用 Docker 稳定部署;

  • 设置页可以配置自己的服务地址,适合自托管;

  • 第一版范围可控,后续还能再考虑和 OpenBitFun 账号/Relay 体系统一。

如果只想快速得到一个可用 MVP,我会按这个顺序做:

  1. 先跑通 Zepp Settings App -> Side Service -> /v1/health;

  2. 再实现 Gateway 配对和任务提交;

  3. 接 openbitfun dispatch;

  4. 最后补手表 UI、轮询和取消。

这样每一步都能独立验证,不会一开始就陷进完整 Agent 协议里。

参考资料