跳到正文
Capriops
Capri FE :5173Capri Host :8765Capri Hub :8787

文档

这十个站点的文案与文档来自同一份内容源。改一次,十个站点一起变;下面这串指纹是内容的哈希,十个站点都印着同一个值。

01 · 总览

Overview

Capri 是什么、三个组件怎么分工、什么时候需要哪一个。

一句话

Grok Build 的远程控制台。Agent 仍然跑在你自己的机器上,浏览器只负责掌控。

Capri(Capricorn)基于 ACP 协议适配 Grok Build,由三个组件组成:一个浏览器前端、一个跑在你机器上的节点、一个可选的中继。

三个组件

组件角色跑在哪需要吗
capri-fe浏览器控制台任何设备的浏览器需要,但通常不用单独部署
capri-hostAgent 节点有 Agent 的那台机器需要
capri-hub中继节点一台能被访问的服务器只有跨网 / 多机才需要

节点二进制里已经嵌好了前端构建产物,所以单机场景只需要跑一个进程:打开 http://localhost:8765 就是完整界面。

两种拓扑

只在一台机器上用:

浏览器 ──▶ capri-host :8765 ──▶ grok

跨网、多台机器:

浏览器 ──▶ capri-hub :8787 ──┬─▶ 家里的节点   ──▶ grok
                              ├─▶ 办公室的节点 ──▶ grok
                              └─▶ 服务器上的节点 ──▶ grok

中继不执行任何命令,只做配对、发现和转发。业务数据落在节点那台机器上。

能做什么

  • 在手机、平板、另一台电脑的浏览器里,继续本机正在跑的会话
  • 左上角切换节点:家里的、办公室的、服务器上的
  • 斜杠命令、权限审批、图片、后台任务、Git / MCP / 记忆——对齐终端界面的常用能力
  • 恢复会话不重放全量历史,打开就能接上

Agent 在节点那台机器上自己读文件、跑命令。浏览器只负责掌控。

边界

有几件事请事先知道:

  • 三端版本要对齐。 事件契约是一套的,前端、中继、节点要一起升级或一起回退,否则会出现重复输出或状态错乱。
  • 中继是入口,不是保险箱。 生产环境必须设门禁密钥,并把跨域来源收到前端真实源。
  • 门禁密钥不进构建产物。 它只存在你这台浏览器里,不要写进前端环境变量、也不要打进静态包。

下一步:本机上手

02 · 本机上手

Quick start

一台机器、一个进程、一个端口,五分钟跑起来。

前置

依赖说明
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

常用的几个:

变量默认说明
PORT8765HTTP 端口(界面 + 接口)
GROK_BINgrokGrok Build 可执行文件
HOST_NAMELocal Host界面上显示的设备名
FE_TOKEN入口鉴权;设了之后浏览器首次打开要输入

完整清单见环境变量

四、放到后台

nohup ./capri-host >> capri-host.log 2>&1 & echo $! > capri-host.pid

要开机自启,见日常运维

接下来

03 · 远程与多机

Remote & multi-node

起一台中继,把家里和办公室的机器都接进来,从任何地方选机器。

适用场景

一台服务器跑中继,家里、办公室各跑一个节点,浏览器从任何地方打开、选机器。

浏览器 ──▶ 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_URLFE_TOKEN 重启
  • 已经有 token 的话可以用 HOST_TOKEN 直接给,它优先于配对码

三、打开浏览器

访问中继地址,门禁里输入 FE_TOKEN,然后从左上角切换节点。

密钥存在你的浏览器里。不要写进前端环境变量,也不要打进静态包。

QUIC 与回退

节点到中继默认走 QUIC。UDP 被挡时自动回退 WebSocket,功能一致、只是少了 QUIC 的连接迁移与队头阻塞优势。

如果中继挂在域名上、而中间的代理丢 UDP,可以用 HUB_QUIC_HOST 强制指定 QUIC 的拨号地址。

省流量的小设计

中继模式下,没有任何浏览器订阅时,节点会暂停实时上报。打开页面后自动恢复。所以"打开一片空白"往往不是故障,刷新一下即可。

升级纪律

前端、中继、节点请一起升级。三端事件契约是一套的,只升一端会出现重复输出或状态错乱。

04 · 架构与数据通路

Architecture

一次请求从浏览器到 Agent 走过的每一段,以及每段负责什么。

分层

浏览器(前端)
   │  HTTP / 事件流
   ▼
中继 :8787            ← 可选;配对、发现、转发
   │  QUIC UDP :8788(回退 WebSocket)
   ▼
节点 :8765           ← 你的机器;界面 + 接口同端口
   │  stdio JSON-RPC(ACP)
   ▼
内核 grok              ← 真正读文件、跑命令的那个进程

每一段做什么

浏览器 → 中继 / 节点

普通 HTTP 接口加一条事件流:请求走 /api/*,实时输出走事件流。门禁密钥在这一层校验,密钥只存在浏览器本地。

单机场景这一段直接打到节点,没有中继参与。

中继 → 节点

一条长连接,默认 QUIC,UDP 不通则回退 WebSocket。中继在这条连接上转发两个方向的消息:

  • 下行:浏览器的指令、权限审批结果
  • 上行:会话事件、工具调用、权限请求、后台任务状态

中继本身不执行命令、不落业务数据。它知道"哪台节点在线",不知道你的代码。

节点 → 内核

节点用标准输入输出跟内核进程说话,走 JSON-RPC,语义遵循 ACP。节点在这里做三件超出"透传"的事:

  1. 能力协商:连接时向内核声明客户端能力,并把内核的会话事件规整成前端契约
  2. 会话续传:恢复会话时不重放全量历史
  3. 服务端裁剪:把回滚后的死分支在服务端就去掉,前端不用自己判断

端口一览

端口协议属于用途
8765HTTP节点界面 + 接口
8787HTTP中继浏览器入口
8788UDP中继节点主通道(QUIC)
5173HTTP前端仅开发服务器

为什么前端嵌在节点里

因为绝大多数人只有一台机器。把构建产物嵌进二进制,单机上手就只剩"下载、运行、打开浏览器"三步,没有反向代理、没有静态目录、没有跨域配置。

要跨网时再把中继加进来——那时前端可以由中继来托管,节点里那份就闲置了。

事件契约是一套的

三端共享同一份事件契约。这带来一条硬性纪律:

前端、中继、节点一起升,或者一起回。只升一端会出现重复输出或状态错乱。

05 · 环境变量

Environment

三个组件的全部环境变量、默认值与含义。

怎么给

都是普通环境变量,随手前置即可:

PORT=9000 HOST_NAME="我的 Mac" ./capri-host

要长期生效,写进服务单元或启动脚本,见日常运维

全部变量

节点

变量默认说明
PORT8765HTTP 端口(界面 + 接口)
GROK_BINgrokGrok Build 可执行文件
HOST_IDlocal多节点时用来区分
HOST_NAMELocal Host界面上显示的名字
FE_TOKEN入口鉴权;与中继同语义,建议同值
HUB_URL设置后进入中继模式
HUB_PAIR_CODE一次性配对码
HOST_TOKEN已配对 token,优先于配对码
XAI_API_KEY可选;否则用内核自带登录
HUB_QUIC_HOST强制 QUIC 拨号地址(代理丢 UDP 时用)

中继

变量默认说明
PORT8787HTTP 端口
QUIC_PORT8788节点主通道 UDP 端口
FE_TOKEN浏览器访问密钥,生产必设
REQUIRE_FE_TOKEN设为 1 时,没配密钥会拒绝启动
CORS_ORIGINS*生产写成前端真实源

前端

变量默认说明
VITE_PROXY_TARGEThttp://localhost:8765开发代理目标

三条约定

门禁密钥两端同值。 节点和中继的 FE_TOKEN 是同一个语义(入口鉴权),部署时建议配成同一个值,浏览器只需要记一个。

配对码是一次性的。 配对成功后 token 落在 ~/.capri-host/hub.json,之后启动不需要再带 HUB_PAIR_CODE。想跳过配对流程可以直接给 HOST_TOKEN

跨域别留星号。 CORS_ORIGINS 默认放开是为了本地方便,生产要写成前端真实源。

前端的那一个

前端只有一个构建期变量:

变量默认说明
VITE_PROXY_TARGEThttp://localhost:8765开发服务器的代理目标

门禁密钥不要写进 VITE_*,也不要打进静态包。它属于浏览器,不属于构建产物。

06 · 前端开发

Front-end dev

本地跑前端、把代理指到中继、以及把新界面塞回节点二进制。

前置

本机要先有一个能连上的节点(或中继)。

跑起来

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 · 日常运维

Operations

后台运行、开机自启、端口占用、升级顺序。

后台运行

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

端口与防火墙

要放行协议什么时候
8765TCP想从局域网直连节点
8787TCP中继的浏览器入口
8788UDP想让节点走 QUIC;不放行则自动回退

端口被占:查一下监听进程,或者换 PORT 启动。

升级顺序

事件契约三端共享,所以:

  1. 停浏览器页面(或者接受短暂错乱)
  2. 中继、节点、前端一起换到同一版本
  3. 重启中继,再重启各节点
  4. 刷新浏览器

只升一端的典型症状是重复输出状态错乱。遇到就把三端拉回同一版本。

日志看什么

  • 中继启动日志里有 6 位配对码
  • 节点配对成功会写 ~/.capri-host/hub.json
  • QUIC 失败会有回退到 WebSocket 的记录——这是正常降级,不是故障

08 · 排障

Troubleshooting

常见现象与处理,按"先看哪里"排序。

快查表

现象处理
配对一直失败码填错或已过期。在中继上轮换配对码,节点带新码重启。
重启还要重新配对看那台机器上的配对记录文件是否还在,以及中继地址是否和当初一致。
中继模式打开没内容没有浏览器订阅时节点会暂停实时上报以省流量,打开页面后自动恢复;不行就刷新。
重复输出、状态错乱三端版本没对齐。事件契约是一套的,前端、中继、节点要一起升或一起回。
QUIC 连不上UDP 端口被挡,会自动回退 WebSocket,功能不受影响。域名经代理丢 UDP 时可强制拨号地址。
端口被占用查一下监听该端口的进程,或者换一个端口启动。
找不到内核可执行文件先完成内核登录,或用环境变量指定可执行文件的绝对路径。
浏览器一直要密钥中继或节点设了门禁密钥,在页面门禁里输入同一个值。密钥只存在你这台浏览器里。
前端白屏或还是旧界面重新构建前端并把产物拷进节点的内嵌目录,然后重新编译、重启节点。

排查顺序

遇到问题,按这个顺序看,通常两步之内就定位:

一、三端版本是否对齐。 重复输出、状态错乱、按钮点了没反应,九成是版本没对齐。事件契约是一套的,先把前端、中继、节点拉到同一版本。

二、门禁密钥是否一致。 页面反复要密钥,说明中继或节点设了 FE_TOKEN 而输入的值不同。建议两端配成同一个值。

三、节点是否真的在线。 中继只做转发,它不知道节点内部发生了什么。先在节点那台机器上直连 http://localhost:8765 验证本地是否正常,再回来看中继。

四、是不是"省流量"在起作用。 中继模式下没有浏览器订阅时,节点会暂停实时上报。打开页面自动恢复,刷新一下即可。

不是故障的几种情况

  • QUIC 连不上、日志里回退 WebSocket:UDP 被挡而已,功能一致
  • 打开页面先是空的、随即补上:会话恢复不重放全量历史,是刻意设计
  • 重启节点没再要配对码:token 已经在 ~/.capri-host/hub.json 里了

还是不行

带上这几样信息更容易定位:

  1. 三端各自的版本号
  2. 拓扑(单机直连,还是经中继)
  3. 节点启动日志的前 20 行
  4. 浏览器控制台的报错