文档
这十个站点的文案与文档来自同一份内容源。改一次,十个站点一起变;下面这串指纹是内容的哈希,十个站点都印着同一个值。
没有匹配的内容
01
总览
Capri 是什么、三个组件怎么分工、什么时候需要哪一个。
一句话
Grok Build 的远程控制台。Agent 仍然跑在你自己的机器上,浏览器只负责掌控。
Capri(Capricorn)基于 ACP 协议适配 Grok Build,由三个组件组成:一个浏览器前端、一个跑在你机器上的节点、一个可选的中继。
三个组件
| 组件 | 角色 | 跑在哪 | 需要吗 |
|---|
capri-fe | 浏览器控制台 | 任何设备的浏览器 | 需要,但通常不用单独部署 |
capri-host | Agent 节点 | 有 Agent 的那台机器 | 需要 |
capri-hub | 中继节点 | 一台能被访问的服务器 | 只有跨网 / 多机才需要 |
节点二进制里已经嵌好了前端构建产物,所以单机场景只需要跑一个进程:打开 http://localhost:8765 就是完整界面。
两种拓扑
只在一台机器上用:
浏览器 ──▶ capri-host :8765 ──▶ grok
跨网、多台机器:
浏览器 ──▶ capri-hub :8787 ──┬─▶ 家里的节点 ──▶ grok
├─▶ 办公室的节点 ──▶ grok
└─▶ 服务器上的节点 ──▶ grok
中继不执行任何命令,只做配对、发现和转发。业务数据落在节点那台机器上。
能做什么
- 在手机、平板、另一台电脑的浏览器里,继续本机正在跑的会话
- 左上角切换节点:家里的、办公室的、服务器上的
- 斜杠命令、权限审批、图片、后台任务、Git / MCP / 记忆——对齐终端界面的常用能力
- 恢复会话不重放全量历史,打开就能接上
Agent 在节点那台机器上自己读文件、跑命令。浏览器只负责掌控。
边界
有几件事请事先知道:
- 三端版本要对齐。 事件契约是一套的,前端、中继、节点要一起升级或一起回退,否则会出现重复输出或状态错乱。
- 中继是入口,不是保险箱。 生产环境必须设门禁密钥,并把跨域来源收到前端真实源。
- 门禁密钥不进构建产物。 它只存在你这台浏览器里,不要写进前端环境变量、也不要打进静态包。
下一步:本机上手。
02
本机上手
一台机器、一个进程、一个端口,五分钟跑起来。
前置
| 依赖 | 说明 |
|---|
| Grok Build CLI | 已安装并登录,或设置 XAI_API_KEY,或配置第三方模型。见 https://x.ai/cli |
| Go ≥ 1.26 | 只有从源码构建才需要;跑 Release 二进制不需要 |
一、跑起来
从 Releases 选你的平台,下载后:
chmod +x ./capri-host # 按实际文件名
./capri-host
或者从源码:
git clone https://github.com/AgentsHarness/capri-host.git
cd capri-host
go run ./cmd/capri-host
二、打开界面
浏览器访问 http://localhost:8765。
界面是节点自己端出来的——一个进程、一个端口同时提供 Web 界面和接口,不用 nginx,不用另起静态服务器。
三、按需改几个参数
PORT=9000 HOST_NAME="我的 Mac" GROK_BIN=/opt/grok/bin/grok ./capri-host
常用的几个:
| 变量 | 默认 | 说明 |
|---|
PORT | 8765 | HTTP 端口(界面 + 接口) |
GROK_BIN | grok | Grok Build 可执行文件 |
HOST_NAME | Local Host | 界面上显示的设备名 |
FE_TOKEN | — | 入口鉴权;设了之后浏览器首次打开要输入 |
完整清单见环境变量。
四、放到后台
nohup ./capri-host >> capri-host.log 2>&1 & echo $! > capri-host.pid
要开机自启,见日常运维。
接下来
03
远程与多机
起一台中继,把家里和办公室的机器都接进来,从任何地方选机器。
适用场景
一台服务器跑中继,家里、办公室各跑一个节点,浏览器从任何地方打开、选机器。
浏览器 ──▶ capri-hub :8787 ──▶ capri-host × N ──▶ grok
一、起中继
你需要一台能够连接公网的服务器。 编译需要 Go 1.26+,或者直接下载 Release 二进制部署。
FE_TOKEN=$(openssl rand -hex 24) go run ./cmd/capri-hub
- HTTP 监听
:8787,节点主通道 QUIC UDP :8788 - 安全组放行 UDP 更稳;不放行会自动回退 WebSocket,功能不受影响
- 启动日志里有 6 位配对码,15 分钟有效
生产务必设置 FE_TOKEN(浏览器门禁)。想强制这条纪律,把 REQUIRE_FE_TOKEN=1 一起加上——没配密钥时它会直接拒绝启动。
随时查看或换新配对码:
curl http://127.0.0.1:8787/api/pairing
curl -X POST http://127.0.0.1:8787/api/pairing/rotate
二、把节点接进来
在每台有 Agent 的机器上:
HUB_URL=http://your-relay:8787
HUB_PAIR_CODE=XXXXXX
HOST_ID=pc
HOST_NAME="家里的 Mac"
FE_TOKEN=XXXXXX
nohup ./capri-host >> capri-host.log 2>&1 & echo $! > capri-host.pid
要点:
- 每台机器给一个不同的
HOST_ID,这是多机之间的区分依据 - 配对成功后 token 写进
~/.capri-host/hub.json,之后只需要带 HUB_URL 和 FE_TOKEN 重启 - 已经有 token 的话可以用
HOST_TOKEN 直接给,它优先于配对码
三、打开浏览器
访问中继地址,门禁里输入 FE_TOKEN,然后从左上角切换节点。
密钥存在你的浏览器里。不要写进前端环境变量,也不要打进静态包。
QUIC 与回退
节点到中继默认走 QUIC。UDP 被挡时自动回退 WebSocket,功能一致、只是少了 QUIC 的连接迁移与队头阻塞优势。
如果中继挂在域名上、而中间的代理丢 UDP,可以用 HUB_QUIC_HOST 强制指定 QUIC 的拨号地址。
省流量的小设计
中继模式下,没有任何浏览器订阅时,节点会暂停实时上报。打开页面后自动恢复。所以"打开一片空白"往往不是故障,刷新一下即可。
升级纪律
前端、中继、节点请一起升级。三端事件契约是一套的,只升一端会出现重复输出或状态错乱。
04
架构与数据通路
一次请求从浏览器到 Agent 走过的每一段,以及每段负责什么。
分层
浏览器(前端)
│ HTTP / 事件流
▼
中继 :8787 ← 可选;配对、发现、转发
│ QUIC UDP :8788(回退 WebSocket)
▼
节点 :8765 ← 你的机器;界面 + 接口同端口
│ stdio JSON-RPC(ACP)
▼
内核 grok ← 真正读文件、跑命令的那个进程
每一段做什么
浏览器 → 中继 / 节点
普通 HTTP 接口加一条事件流:请求走 /api/*,实时输出走事件流。门禁密钥在这一层校验,密钥只存在浏览器本地。
单机场景这一段直接打到节点,没有中继参与。
中继 → 节点
一条长连接,默认 QUIC,UDP 不通则回退 WebSocket。中继在这条连接上转发两个方向的消息:
- 下行:浏览器的指令、权限审批结果
- 上行:会话事件、工具调用、权限请求、后台任务状态
中继本身不执行命令、不落业务数据。它知道"哪台节点在线",不知道你的代码。
节点 → 内核
节点用标准输入输出跟内核进程说话,走 JSON-RPC,语义遵循 ACP。节点在这里做三件超出"透传"的事:
- 能力协商:连接时向内核声明客户端能力,并把内核的会话事件规整成前端契约
- 会话续传:恢复会话时不重放全量历史
- 服务端裁剪:把回滚后的死分支在服务端就去掉,前端不用自己判断
端口一览
| 端口 | 协议 | 属于 | 用途 |
|---|
8765 | HTTP | 节点 | 界面 + 接口 |
8787 | HTTP | 中继 | 浏览器入口 |
8788 | UDP | 中继 | 节点主通道(QUIC) |
5173 | HTTP | 前端 | 仅开发服务器 |
为什么前端嵌在节点里
因为绝大多数人只有一台机器。把构建产物嵌进二进制,单机上手就只剩"下载、运行、打开浏览器"三步,没有反向代理、没有静态目录、没有跨域配置。
要跨网时再把中继加进来——那时前端可以由中继来托管,节点里那份就闲置了。
事件契约是一套的
三端共享同一份事件契约。这带来一条硬性纪律:
前端、中继、节点一起升,或者一起回。只升一端会出现重复输出或状态错乱。
05
环境变量
三个组件的全部环境变量、默认值与含义。
怎么给
都是普通环境变量,随手前置即可:
PORT=9000 HOST_NAME="我的 Mac" ./capri-host
要长期生效,写进服务单元或启动脚本,见日常运维。
全部变量
节点
| 变量 | 默认 | 说明 |
|---|
PORT | 8765 | HTTP 端口(界面 + 接口) |
GROK_BIN | grok | Grok Build 可执行文件 |
HOST_ID | local | 多节点时用来区分 |
HOST_NAME | Local Host | 界面上显示的名字 |
FE_TOKEN | — | 入口鉴权;与中继同语义,建议同值 |
HUB_URL | — | 设置后进入中继模式 |
HUB_PAIR_CODE | — | 一次性配对码 |
HOST_TOKEN | — | 已配对 token,优先于配对码 |
XAI_API_KEY | — | 可选;否则用内核自带登录 |
HUB_QUIC_HOST | — | 强制 QUIC 拨号地址(代理丢 UDP 时用) |
中继
| 变量 | 默认 | 说明 |
|---|
PORT | 8787 | HTTP 端口 |
QUIC_PORT | 8788 | 节点主通道 UDP 端口 |
FE_TOKEN | — | 浏览器访问密钥,生产必设 |
REQUIRE_FE_TOKEN | — | 设为 1 时,没配密钥会拒绝启动 |
CORS_ORIGINS | * | 生产写成前端真实源 |
前端
| 变量 | 默认 | 说明 |
|---|
VITE_PROXY_TARGET | http://localhost:8765 | 开发代理目标 |
三条约定
门禁密钥两端同值。 节点和中继的 FE_TOKEN 是同一个语义(入口鉴权),部署时建议配成同一个值,浏览器只需要记一个。
配对码是一次性的。 配对成功后 token 落在 ~/.capri-host/hub.json,之后启动不需要再带 HUB_PAIR_CODE。想跳过配对流程可以直接给 HOST_TOKEN。
跨域别留星号。 CORS_ORIGINS 默认放开是为了本地方便,生产要写成前端真实源。
前端的那一个
前端只有一个构建期变量:
| 变量 | 默认 | 说明 |
|---|
VITE_PROXY_TARGET | http://localhost:8765 | 开发服务器的代理目标 |
门禁密钥不要写进 VITE_*,也不要打进静态包。它属于浏览器,不属于构建产物。
06
前端开发
本地跑前端、把代理指到中继、以及把新界面塞回节点二进制。
前置
本机要先有一个能连上的节点(或中继)。
跑起来
npm install
npm run dev
打开 http://localhost:5173。开发服务器会把接口与事件流代理到本机节点 http://localhost:8765。
指到中继(同时看好几台机器)
VITE_PROXY_TARGET=http://your-relay:8787 npm run dev
如果中继设了门禁密钥,页面会弹出密钥框。密钥只存在你这台浏览器里。
技术栈
React 19 · TypeScript · Vite · Tailwind · Zustand。
npm run build # 类型检查 + 打包
npm run lint # 静态检查
把新界面塞回节点
节点二进制里嵌着前端的构建产物。要换新界面:
cd capri-fe && npm run build
cp -R dist ../capri-host/internal/server/web/dist
然后重新编译、重启节点。
换完记得对齐版本:三端事件契约是一套的,只升前端会出现重复输出或状态错乱。
密钥的处置
只有一条规则,但很重要:
- ✅ 运行时由用户在页面门禁里输入,存在浏览器本地
- ❌ 写进
VITE_* 环境变量 - ❌ 打进静态包、提交进仓库
07
日常运维
后台运行、开机自启、端口占用、升级顺序。
后台运行
nohup ./capri-host >> capri-host.log 2>&1 & echo $! > capri-host.pid
停:
kill "$(cat capri-host.pid)"
开机自启 · macOS
~/Library/LaunchAgents/com.capri.host.plist:
<?xml version="1.0" encoding="UTF-8"?>
<plist version="1.0">
<dict>
<key>Label</key><string>com.capri.host</string>
<key>ProgramArguments</key>
<array><string>/绝对路径/capri-host</string></array>
<key>EnvironmentVariables</key>
<dict>
<key>HOST_NAME</key><string>我的 Mac</string>
<key>HUB_URL</key><string>http://your-relay:8787</string>
</dict>
<key>RunAtLoad</key><true/>
<key>KeepAlive</key><true/>
<key>StandardOutPath</key><string>/绝对路径/capri-host.log</string>
<key>StandardErrorPath</key><string>/绝对路径/capri-host.log</string>
</dict>
</plist>
开机自启 · Linux
/etc/systemd/system/capri-host.service:
[Unit]
Description=capri agent node
After=network-online.target
[Service]
ExecStart=/绝对路径/capri-host
Restart=always
Environment=HOST_NAME=我的服务器
[Install]
WantedBy=multi-user.target
端口与防火墙
| 要放行 | 协议 | 什么时候 |
|---|
8765 | TCP | 想从局域网直连节点 |
8787 | TCP | 中继的浏览器入口 |
8788 | UDP | 想让节点走 QUIC;不放行则自动回退 |
端口被占:查一下监听进程,或者换 PORT 启动。
升级顺序
事件契约三端共享,所以:
- 停浏览器页面(或者接受短暂错乱)
- 中继、节点、前端一起换到同一版本
- 重启中继,再重启各节点
- 刷新浏览器
只升一端的典型症状是重复输出或状态错乱。遇到就把三端拉回同一版本。
日志看什么
- 中继启动日志里有 6 位配对码
- 节点配对成功会写
~/.capri-host/hub.json - QUIC 失败会有回退到 WebSocket 的记录——这是正常降级,不是故障
08
排障
常见现象与处理,按"先看哪里"排序。
快查表
| 现象 | 处理 |
|---|
| 配对一直失败 | 码填错或已过期。在中继上轮换配对码,节点带新码重启。 |
| 重启还要重新配对 | 看那台机器上的配对记录文件是否还在,以及中继地址是否和当初一致。 |
| 中继模式打开没内容 | 没有浏览器订阅时节点会暂停实时上报以省流量,打开页面后自动恢复;不行就刷新。 |
| 重复输出、状态错乱 | 三端版本没对齐。事件契约是一套的,前端、中继、节点要一起升或一起回。 |
| QUIC 连不上 | UDP 端口被挡,会自动回退 WebSocket,功能不受影响。域名经代理丢 UDP 时可强制拨号地址。 |
| 端口被占用 | 查一下监听该端口的进程,或者换一个端口启动。 |
| 找不到内核可执行文件 | 先完成内核登录,或用环境变量指定可执行文件的绝对路径。 |
| 浏览器一直要密钥 | 中继或节点设了门禁密钥,在页面门禁里输入同一个值。密钥只存在你这台浏览器里。 |
| 前端白屏或还是旧界面 | 重新构建前端并把产物拷进节点的内嵌目录,然后重新编译、重启节点。 |
排查顺序
遇到问题,按这个顺序看,通常两步之内就定位:
一、三端版本是否对齐。 重复输出、状态错乱、按钮点了没反应,九成是版本没对齐。事件契约是一套的,先把前端、中继、节点拉到同一版本。
二、门禁密钥是否一致。 页面反复要密钥,说明中继或节点设了 FE_TOKEN 而输入的值不同。建议两端配成同一个值。
三、节点是否真的在线。 中继只做转发,它不知道节点内部发生了什么。先在节点那台机器上直连 http://localhost:8765 验证本地是否正常,再回来看中继。
四、是不是"省流量"在起作用。 中继模式下没有浏览器订阅时,节点会暂停实时上报。打开页面自动恢复,刷新一下即可。
不是故障的几种情况
- QUIC 连不上、日志里回退 WebSocket:UDP 被挡而已,功能一致
- 打开页面先是空的、随即补上:会话恢复不重放全量历史,是刻意设计
- 重启节点没再要配对码:token 已经在
~/.capri-host/hub.json 里了
还是不行
带上这几样信息更容易定位:
- 三端各自的版本号
- 拓扑(单机直连,还是经中继)
- 节点启动日志的前 20 行
- 浏览器控制台的报错