mcpField
H
hoptell

hoptell

本地服务
github.com/EminUZUNAI 与智能体101 天前更新

关于 hoptell

hoptell 是可自托管的中继服务,让支持 MCP 的编码 Agent 能跨会话和设备交换消息。其 MCP 服务提供同伴与收件箱工具,部分投递方式可唤醒空闲 Agent。

主要功能

  1. 01在 Agent 同伴间中继消息
  2. 02列出同伴并发送消息
  3. 03读取收件箱并确认消息
  4. 04唤醒受支持的空闲会话
  5. 05供脚本和 CI 使用的命令行

适用场景

  1. 01请另一台设备上的 Agent 审查代码
  2. 02按角色或跨团队协调多个 Agent
  3. 03通过本地中继在 Agent 会话间传递消息

MCP 相关

每个接入的 Agent 会话运行 hoptell 的 MCP 服务,提供同伴列表、消息发送、收件箱、等待和确认工具。该服务连接到部署在 LAN 或 VPN 上的中继。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"eminuzun-hoptell": {
"command": "npx",
"args": [
"-y",
"hoptell"
],
"env": {
"HOPTELL_RELAY": "<HOPTELL_RELAY>",
"HOPTELL_TOKEN": "<HOPTELL_TOKEN>",
"HOPTELL_NAME": "<HOPTELL_NAME>",
"HOPTELL_ROLES": "<HOPTELL_ROLES>",
"HOPTELL_AUTH": "<HOPTELL_AUTH>",
"HOPTELL_TITLE": "<HOPTELL_TITLE>"
}
}
}
}
启动命令npx -y hoptell
需要配置的环境变量
HOPTELL_RELAYRelay URL, e.g. wss://relay.example.com; not needed with a settings or relays file
HOPTELL_TOKEN密钥Your member token or the relay's shared token; not needed with HOPTELL_AUTH=login
HOPTELL_NAMEThis agent's peer name (letters, digits, _ and -)
HOPTELL_ROLESComma-separated roles for @role messages
HOPTELL_AUTH密钥login: sign in with hoptell login instead of a token
HOPTELL_TITLEA short title shown next to this session's name in list_peers

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npmhoptell0.3.2

工具

还没有获取到工具列表

本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。

详细介绍

hoptell 图标 hoptell

让 AI 编码 Agent 彼此交流:跨会话、设备、账号和工具。

hoptell 通过一个由你在局域网或 VPN 上自行运行的小型中继,连接 Claude Code、Codex、Antigravity (agy) 及其他支持 MCP 的 Agent。Agent 可以使用工具列出对等 Agent 并发送消息;收到的消息还会唤醒空闲的 Agent,因此,你笔记本上的 Claude 会话可以把审查任务交给同事工作站上的 Codex 会话,并在无需任何人输入的情况下收到回复。

Sam 的 Claude Code 将 greet.js 的内容发送给 Alex 的 Codex;Codex 被唤醒并建议一行修正

Sam 在 MacBook 上的 Claude Code 将 greet.js 的内容发送给 Linux 容器中 Alex 的 Codex。Codex 被唤醒,并返回一行修复建议。实际会话,以 2.5× 倍速播放,并缩短了空闲暂停时间。

 laptop:   Claude Code ──┐                        ┌── Codex        :workstation
 laptop:   Codex       ──┼── hoptell relay (LAN) ─┼── Claude Code  :workstation
 CI box:   Claude Code ──┘    one tiny process    └── ...
  • 可自托管,无需账号。 一个 Node 进程。Agent 可以使用不同的 Claude 或 OpenAI 账号。Agent 之间的消息只通过你的中继传输;每个 Agent 仍像往常一样与自己的 AI 服务提供商通信。
  • 唤醒受支持的空闲会话。 Claude Code 可以通过 hook 或 channel 通知唤醒; tmux 中受支持的终端 Agent 会在提示符中输入一条固定通知;其他客户端则会轮询。
  • 团队和群集。 Agent 会声明自己的角色(reviewer、backend 等)。可以按名称向某个 Agent 发送消息,向所有具有某角色的 Agent 发送消息(@reviewer),或向所有人发送消息(@all)。
  • 轻巧且易于审查。 使用 JavaScript 编写,运行时直接依赖项只有四个:ws、MCP SDK、 jose 和 openid-client。后两个仅在通过身份提供商登录时加载。
  • 审查者和你的手机。 Claude Code 插件会启动审查 Agent 并收集其结论;你的 Agent 也可以通过可选的 Telegram 所有者频道向你提问。

Agent 也可以回答有关计划的问题,而不只是代码:

Sam 的 Claude Code 询问 @pm 关于演示应用 2.4 版本的问题;Dana 的 Agent 根据 planning/roadmap.md 作答

Sam 的 Claude Code 询问 @pm 关于演示应用 2.4 版本的问题,Dana 的 Agent 则根据其 Linux 容器中的 planning/roadmap.md 作答。实际会话,以 2.5× 倍速播放,并缩短了空闲暂停时间。

hoptell 会在可能采取行动的 Agent 之间传递纯文本。在连接以宽松权限运行的 Agent 之前,请阅读安全性。

从这里开始:

  1. 安装并发送第一条消息
  2. 与审查 Agent 协作
  3. 通过 Telegram 联系你的 Agent
  4. 了解安全边界

有关配置详情,请参阅多个中继、 团队和群集、联系人(谁可以联系你的会话) 以及 CLI 参考。

工作原理

部分功能
hoptell relay在一台机器上运行的 WebSocket 中心:对等端进行身份验证、路由消息,并将消息排入离线对等端的队列(存储在内存中)。
hoptell mcp每个 Agent 会话运行的 MCP 服务:提供 list_peers、send_message、wait_for_message、read_inbox、ack_messages 工具。
hoptell tmux在 tmux 中运行终端 Agent,并在其提示词似乎处于空闲状态时,尝试提交固定的收件箱通知。
hoptell send / list / wait / listen / ack为脚本、CI 任务和不使用 MCP 的 Agent 提供的 CLI。

传入消息如何到达 Agent:

Agent启动方式传入消息
Claude Code(channel)claude --dangerously-load-development-channels server:hoptellchannel 通知会唤醒 Agent,随后 Agent 调用 read_inbox 读取消息
Claude Code(hooks)使用 hoptell 插件运行 claude,或使用 hoptell hooks 提供的设置固定的 hook 通知会唤醒 Agent,随后 Agent 调用 read_inbox
Claude Code(listener)HOPTELL_PUSH=listener claudeMCP 服务会要求 Claude 持续在后台运行 hoptell listen;命令返回时,Claude 可以被唤醒
Antigravity(agy)agy在后台运行 hoptell listen,与原生 Claude Code 相同
tmux 中受支持的终端 Agenthoptell tmux <name> -- codex固定通知会输入到 Agent 的提示词中;Agent 调用 read_inbox
其他任意对象—使用 wait_for_message / read_inbox 工具,或运行 hoptell wait

hoptell 会在会话启动时选择投递模式(由 HOPTELL_PUSH 设置)。通知中绝不包含消息文本,而且通知并不能证明 Agent 已读取消息。请参阅投递模式详解。

快速开始

要求:Node.js 22+(运行 relay 需要 22.13+,在线备份需要 22.16+),以及 tmux 3.2+,用于唤醒 Codex/终端 Agent。支持 macOS 和 Linux;Windows 上仅 relay 和轮询工具可用。

1. 安装(每台机器都要安装)
npm install -g hoptell    # puts `hoptell` on your PATH

也可以从源代码安装:git clone https://github.com/EminUZUN/hoptell && cd hoptell && npm install && npm link。

**Claude Code:**安装插件(见下文),并跳过第 4 步中的手动注册。插件会询问 relay URL 和令牌,并将令牌存储在 Claude Code 的安全存储中。hoptell CLI 和 hoptell tmux 需要 npm 包;relay 需要 npm 包或 Docker 镜像。

安装插件。使用自动投递时,如果启动检查成功,直接运行 claude 会使用 hooks;添加 channel 标志即可使用 channels:

/plugin marketplace add EminUZUN/hoptell
/plugin install hoptell@hoptell
claude                                                                    # hook notices
claude --dangerously-load-development-channels plugin:hoptell@hoptell   # channel notices

relay 镜像为 ghcr.io/eminuzun/hoptell,该服务已在 MCP Registry 中列出,名称为 io.github.EminUZUN/hoptell。

2. 启动 relay(一台机器)
mkdir -p ~/.config/hoptell
cat > ~/.config/hoptell/.env <<EOF
HOPTELL_TOKEN=$(openssl rand -hex 32)
HOPTELL_HOST=192.0.2.10
EOF
chmod 600 ~/.config/hoptell/.env
hoptell relay

在上方的 relay 设置中,将 192.0.2.10 替换为此机器的 LAN 或 VPN 地址。使用 Docker 时,也使用相同地址,并将 ... 替换为生成的令牌:docker run -d -p 192.0.2.10:7777:7777 -v hoptell-state:/data -e HOPTELL_TOKEN=... ghcr.io/eminuzun/hoptell。仅在该地址上公开端口。若未指定主机地址,-p 7777:7777 默认会在所有主机地址上公开端口。请参阅 examples/。健康检查:GET /healthz。

relay 会保留一个小型状态目录(默认路径为 ~/.local/state/hoptell-relay;可使用 --state-dir 或 HOPTELL_STATE_DIR 设置)。该目录包含 relay 的 realm id(客户端用于识别此 relay 命名空间的随机 ID)以及带有审计日志的数据库,但不包含明文消息。升级或迁移 relay 时,请保留此目录:如果 realm id 发生变化,之前连接过的客户端会拒绝连接,直到重置其配置文件。Docker 镜像将其保存在 /data 中;请像上面那样使用命名卷,以便替换容器后仍能保留相同的 realm id。

3. 配置每台机器

将以下设置添加到 ~/.config/hoptell/.env(chmod 600)。在中继机器上,将它们添加到第 2 步的文件中,并保留其中的 HOPTELL_HOST 和令牌:

HOPTELL_RELAY=ws://192.0.2.10:7777
HOPTELL_TOKEN=<the same token>

检查:hoptell list 应能连接并打印对等端(目前还没有)。

4. 连接你的 Agent

使用插件的 Claude Code(来自第 1 步):启动 claude。插件的启动检查成功后,Hook 通知会唤醒它;否则 hoptell 会回退到后台监听器(hoptell doctor 会显示使用了哪种方式)。如需使用频道通知,请改为启动 claude --dangerously-load-development-channels plugin:hoptell@hoptell。

**不使用插件的 Claude Code:**注册一次 MCP 服务(用户范围,适用于所有项目),然后将 hoptell hooks 输出的 Hook 设置添加到 ~/.claude/settings.json:

claude mcp add --scope user hoptell -- hoptell mcp
hoptell hooks
HOPTELL_NAME=laptop-claude claude

请勿在同一会话中同时使用插件和 claude mcp add。

如果 Agent 找不到 hoptell(例如使用 nvm 时),请在此处及以下配置中使用 command -v hoptell 输出的完整路径。

在此仓库的克隆副本中,.mcp.json 会为你注册该服务。

Codex:添加到 ~/.codex/config.toml:

[mcp_servers.hoptell]
command = "hoptell"
args = ["mcp"]
tool_timeout_sec = 1800                  # wait_for_message can block up to 1500s
default_tools_approval_mode = "approve"  # optional: no approval prompt per hoptell tool call

然后通过 tmux 启动 Codex,以便消息能唤醒它:

hoptell tmux laptop-codex -- codex

启动器会通过 -c 覆盖选项将对等端名称传给 Codex,因为交互式 Codex 会从共享守护进程启动 MCP 服务,而该进程不会继承你的 Shell 环境。使用 Ctrl-b d 分离会话,使用 tmux attach -t '=hoptell-laptop-codex' 重新连接。

Antigravity(agy):注册一次 MCP 服务:

agy mcp add hoptell hoptell mcp
HOPTELL_NAME=laptop-agy agy                      # listener mode, after your first prompt
hoptell tmux laptop-agy --roles gemini -- agy    # or: woken through tmux
5. 试用

向任一 Agent 提问:“列出 hoptell 对等端,并向 laptop-codex 打个招呼。”

在全新安装中,上述共享令牌设置会暂存问候消息,直到你允许发送方。在接收消息的机器上,运行 hoptell contacts,然后运行 hoptell contacts show <id> 查看正在等待的发送方。检查发送方后,运行输出的 allow-once 命令以放行这条问候消息,或运行 allow 以允许该发送方今后发送的消息。

如果 Claude Code 会话在另一个 Agent 的 send_message 报告消息已送达后仍未唤醒,请让该 Agent 调用 read_inbox:消息会先存入本地收件箱,然后才会发送频道或 Hook 通知。hoptell doctor 会显示传递设置,以及此机器上正在运行的 Hook 会话。

**回执。**使用最新的中继服务时,消息会留在会话的收件箱中,直到 Agent 确认收到。read_inbox 和 wait_for_message 会返回带有回执的一批消息;处理完消息后,Agent 会在下次调用时将回执作为 ack 传回(或调用 ack_messages)。未确认时再次读取会返回同一批消息;如果一批消息在 30 分钟内未获确认(HOPTELL_LEASE_MINUTES),系统会再次显示这些消息,并标记为未确认。确认表示 Agent 已接受这些消息;这并不意味着 Agent 后续的操作只会执行一次。对于无法可靠重复传递某个值的 Agent,可将 HOPTELL_RECEIPTS=off 设为关闭:读取后会像以前一样移除消息。一个会话的收件箱最多可容纳 1,000 条消息或 50 MB;超出后,新消息会暂存在中继服务中,直到有消息获确认。

多个中继

一个 Agent 会话可以同时连接多个中继,例如你团队的中继和你自己的中继。将它们列在中继文件 ~/.config/hoptell/relays.json 中(或使用 HOPTELL_RELAYS;权限设为 0600):

{
  "version": 1,
  "default": "work",
  "relays": [
    { "alias": "work", "url": "wss://relay.example.com", "auth": "login" },
    { "alias": "lab", "url": "wss://lab.example.org", "tokenFile": "~/.config/hoptell/lab.token" },
    { "alias": "ops", "url": "wss://ops.example.net", "tokenEnv": "HOPTELL_TOKEN_OPS", "join": false }
  ]
}
  • 使用 name@alias(也可以使用 @role@alias 和 @all@alias)指定对端。单独的名称只会发送到 default 中继,绝不会发送到恰好知道该名称的其他中继;如果没有默认中继,则拒绝发送单独的名称。消息绝不会从一个中继转发到另一个中继。
  • 消息会显示其来源中继:from "carol@lab"。回复(reply_to)必须通过消息来源的中继发送;如果回复地址指向另一个中继,hoptell 会拒绝,并指出正确的地址。
  • read_inbox 会通过一个回执返回来自所有中继的消息;list_peers 会列出每个中继及其对端,以及是否已连接。使用中继文件时,读取操作始终以回执结束(即使设置了 HOPTELL_RECEIPTS=off):只有确认回执后消息才会被移除,因此读取操作即使被取消或结果丢失,也不会丢失消息。
  • 令牌来自 tokenFile(私有文件)或 tokenEnv,绝不会直接从中继文件中读取;"auth": "login" 会对该中继使用 hoptell login。"join": false 会使中继仅能发送:此会话不会在该中继上线,只会在你向其发送消息时发送。
  • 条目中的 name 会设置该中继上的对端名称;namePrefix(例如 "me-")会在此会话的名称前加上前缀,适用于成员名称必须以此前缀开头的中继。更改文件后,需重新启动会话才能生效。hoptell doctor 会检查该文件。
  • 在命令行中:hoptell send bob@lab hi,或 hoptell send --relay lab bob hi;hoptell list --relay lab;hoptell wait --relay lab。

如果没有中继文件,hoptell 的行为与之前完全相同,只使用一个中继。

在自己的机器上运行私有中继

若要只连接同一台计算机上的你自己的 Agent 会话,请以用户服务的方式运行私有中继:

hoptell local-relay install          # macOS LaunchAgent or Linux systemd user service, port 7788
hoptell local-relay status           # service, sign-in, port and paths; also shown by hoptell doctor
hoptell local-relay stop | start | uninstall [--purge]

它仅监听 127.0.0.1,并接受一个成员令牌。令牌保存在 ~/.hoptell/local-relay/token 中,只有你的账户可读取(机器上的其他账户可以访问端口,但无法登录)。

安装时会将其添加到你的中继文件中(参见多个中继),作为名为 local 的中继(如果需要则创建文件,并将当前中继保留为默认中继),这样会话就能通过 name@local 联系你自己的 Agent;卸载时会移除此条目,除非你修改过它(如果安装时创建的中继文件在移除后为空,也会一并删除)。

该服务只读取自己的设置文件,不读取共享的 hoptell 设置。移动 Node.js 或 hoptell 后(升级到另一个路径时),请再次运行 install;status 会对此发出警告。再次运行会保留令牌和端口(--port 会更改端口,并同步更新中继条目)。如果令牌或其旁边的成员文件丢失,安装会停止,而不会签发新令牌:请恢复文件,或使用 uninstall --purge 重新开始。

此计算机上的 HTTP MCP

推荐使用 stdio。对于仅支持 HTTP 的 MCP 客户端,hoptell 可以在此计算机上提供 MCP 服务 (Streamable HTTP):

hoptell mcp-http client add editor --name alice-editor   # prints the client's token once
hoptell mcp-http [--port 7790]                           # serves http://127.0.0.1:7790/mcp
hoptell mcp-http client list | remove <label>

使用该 URL 和请求头 Authorization: Bearer <token> 配置客户端。每个客户端会话都有自己的 Agent 会话,并使用客户端条目的对等端名称和角色(未命名的条目会自动获得名称,不会使用服务端自身的设置)。每次读取都会保存在回执中,直到收到确认,因此连接中断导致响应丢失时,会再次显示该响应。服务端仅在 127.0.0.1 上响应,且只接受发往该主机的请求;来自网页的请求(带有任何 Origin 请求头)都会被拒绝,并且不会发送 CORS 请求头。移除客户端会结束其会话。限制:每个客户端最多 4 个会话,总计最多 32 个;每个会话最多有 4 个工具调用正在进行(其中一个为 wait_for_message),以及 2 个独立事件流。

唤醒:在初始化时协商了 channel 扩展的客户端会收到 channel 通知;否则,Agent 会通过 read_inbox 或 wait_for_message 读取。对于 Claude Code hook 通知,客户端还需发送 hoptell hook-header 输出的请求头(从客户端命令中运行该命令可设置动态请求头);没有该请求头,HTTP 会话就不会收到 hook 通知。

面向组织

hoptell 没有中央服务:每个组织都运行自己的中继服务,Agent 从用户的计算机连接。

  1. 运行中继服务:在网络内运行 Docker 镜像(examples/docker-compose.yml), 或运行 systemd 单元(examples/hoptell-relay.service),并将其置于 VPN 或 TLS 代理之后。

  2. 签发成员专属令牌:使用成员文件(参见团队与 swarm), 防止用户使用彼此的 Agent 名称。

  3. 部署客户端:使用 Claude Code 插件,或运行 npm install -g hoptell, 再为 Codex 和 Antigravity 配置 MCP。

  4. 将 channel 加入允许列表(Claude Code):通过托管设置,用户无需开发标志及其提示词,即可启动 claude --channels plugin:hoptell@hoptell:

    {
      "channelsEnabled": true,
      "allowedChannelPlugins": [{ "marketplace": "hoptell", "plugin": "hoptell" }]
    }
    
在 TLS 代理后运行中继

hoptell 没有内置 TLS。将中继放在终止 TLS 的反向代理后,并传递 WebSocket 升级请求(Upgrade 和 Connection 标头,使用 HTTP/1.1 连接中继),空闲超时至少设为 60 秒;中继每 15 秒发送一次 ping。客户端随后使用 HOPTELL_RELAY=wss://relay.example.com。

  • 客户端地址: 在代理后,中继看到的是代理的地址。 --trusted-proxies 10.0.0.5 或 HOPTELL_TRUSTED_PROXIES=10.0.0.5 用于配置 可信代理;两者都接受以逗号分隔的 IP 地址或 CIDR 范围列表。 对于来自这些代理的连接,中继会从右向左读取 X-Forwarded-For,并记录 client via proxy。转发的地址仅用于日志。 将中继端口的访问限制为已配置的代理。
  • 日志: --log-format json(或 HOPTELL_LOG_FORMAT=json)会逐行写入一个 JSON 对象 (time、msg)。日志值中的控制字符会被替换,因此值无法另起一行日志。
  • 指标: --metrics-port 9464 或 HOPTELL_METRICS_PORT=9464 会在 127.0.0.1 上启用 Prometheus 文本格式的 GET /metrics。指标包括已连接的对等端、排队的消息和字节数、最早排队消息的 存续时间、未确认的投递,以及登录、令牌拒绝、已接受的发送、投递 尝试、过期、速率限制、队列已满和接管的计数器。指标不包含对等端 名称、消息文本或令牌。将 HOPTELL_METRICS_HOST 设置为绑定到其他地址; 指标会暴露活动水平,因此请谨慎选择有权访问的对象。
  • 检查: 中继运行时,GET /healthz 返回 200。 hoptell doctor --relay-check 会向临时接收端发送测试消息,并报告连接建立、 投递和确认所用的时间。
  • 身份验证模式: 中继会报告名称的身份验证方式,hoptell doctor 也会显示 该方式:一个共享令牌(名称标识会话,而非用户)、仅使用每位成员各自的令牌,或两者并用 (警告:共享令牌持有者可以使用任意成员的名称)。
使用身份提供方登录(OIDC)

无需发放成员令牌,中继可以接受通过组织的身份提供方(Keycloak,或任何能为自定义资源签发访问令牌的提供方)进行的登录。中继会检查为中继签发的访问令牌;成员身份只会经过明确关联,绝不会自动创建。成员令牌仍可继续使用。

  1. 在提供方处(以 Keycloak 为例):创建客户端范围 hoptell,并配置受众映射器,将 hoptell-relay 添加到访问令牌中;创建公共客户端 hoptell-cli(标准流程、PKCE S256、重定向 URI http://127.0.0.1/callback,端口可按 RFC 8252 任意选择;也可选配设备授权),并为其配置该范围;对于无人值守的 Agent,创建带有服务账户(build-bot)的机密客户端。对于不透明令牌或需要立即撤销的情况,还要为中继创建用于令牌内省的机密客户端。test/keycloak/realm.json 是一个完整的测试 Realm。
  2. 在中继处:创建登录配置文件(权限模式为 0600),例如 examples/oidc.example.json,并通过 --oidc <file> 或 HOPTELL_OIDC 传入。validation 可设为 jwt(使用提供方的密钥进行验证)或 introspection(每个令牌都由提供方验证;配置 introspection: {clientId, clientSecretFile})。对于签发 RFC 9068 访问令牌的提供方,profile 设为 rfc9068;否则设为 claims,并配置只有访问令牌才包含的 requiredClaims(Keycloak:{"typ": "Bearer"})。中继会在 /.well-known/hoptell 发布签发方和 CLI 客户端信息(不含机密)。
  3. 关联身份与成员:运行 hoptell members link alice --issuer <issuer> --subject <sub>(填写该用户或服务账户在提供方处的 sub)。该命令会自行重新加载运行中的中继(参见“成员”)。
  4. 在每台机器上:运行 hoptell login 打开浏览器(--device 会显示供另一台设备使用的代码;服务账户可使用 --client-credentials --client-id <id> --client-secret-file <file>),然后设置 HOPTELL_AUTH=login。凭据会保存在操作系统密钥链中(macOS Keychain、Linux Secret Service);也可以使用 --store file,将凭据保存在由 HOPTELL_CREDENTIAL_PASSPHRASE 加密的文件中。hoptell logout 会移除凭据并在提供方处撤销该凭据。

在实时连接上,访问令牌会在过期前续期;令牌过期后,中继会关闭相应连接。JWT 在过期前会一直被中继视为有效,因此退出登录或移除账户会在令牌有效期内生效(有效期受 maxTokenLifetimeSeconds 限制);使用内省时,则会在 maxRevocationDelaySeconds 内生效。hoptell doctor 会显示登录信息并检查登录状态。

Broker 模式:仅负责用户登录的提供方

有些提供方会为客户端签发 ID 令牌,但不会为中继等资源签发访问令牌。 在 broker 模式下,中继本身是提供方的客户端:你在浏览器中登录该提供方, 中继会检查 ID 令牌,并为 hoptell login 签发自己的凭证。仅支持浏览器 登录;设备和服务账户使用成员令牌或资源模式。

  1. 在提供方处:为中继创建一个机密客户端(使用客户端密钥或用于 private_key_jwt 的私钥),重定向 URI 为 <publicBaseUrl>/oidc/<id>/callback,作用域为 openid。
  2. 在中继处:在登录文件中添加一个包含 "mode": "broker" 的签发方条目: id(用于命名中继的端点 /oidc/<id>/...)、publicBaseUrl(中继的公开 https 来源,由运营者配置;绝不会从请求标头中获取)、client (clientId 和 clientSecretFile 或 privateKeyFile),以及可选的 scopes、 reverifyAfterSeconds(经过此时长后需重新在浏览器中登录,默认 1 天)、 familyIdleSeconds(30 天)、familyMaxSeconds(90 天)、accessTokenSeconds(900)和 requireReauthentication。请参阅 examples/oidc.example.json。 中继将登录信息保存在 <state dir>/broker/ 中(每个状态目录只能运行一个中继进程)。
  3. 关联身份,方法同上。身份尚未关联的用户会看到签发方和主题,以便提供给管理员。 同一个人可以拥有不同的 broker 和资源主题。
  4. 在每台机器上:运行 hoptell login(如果中继在两种模式下都提供同一个提供方,则加上 --mode broker),然后设置 HOPTELL_AUTH=login。

刷新令牌只能使用一次:中继会检测已复制且使用过的令牌,并在所有位置终止该登录。 如果续期请求的响应丢失,旧令牌绝不会再次发送,此时需要运行 hoptell login。 hoptell members signout <name>(然后重新加载)会终止该成员截至当前建立的所有 broker 登录;移除或取消关联该成员也会如此。如果中继的 broker/refresh.key 丢失或损坏,broker 登录会保持关闭状态,直到在停止中继的情况下运行 hoptell relay broker-reset --yes,创建新密钥并终止所有 broker 登录。

托管 MCP 端点(实验性)

主机可以读取其租户发送和接收的每条消息。 托管模式允许中继运营方为无法在本地运行 hoptell 的用户提供 MCP 端点(例如,从供应商云端连接的自定义连接器)。此模式未采用端到端加密,并且仍处于实验阶段:hoptell mcp-hosted --experimental。

  • 托管关系。 托管成员拥有自己的成员 ID,且设置为 custody: "hosted";中继的成员文件中还必须有一个主机(hoptell members host add <id>,然后运行 hoptell members add-hosted <name> --host <id>)。托管成员没有令牌和设备;只有其主机可以使用主机自己的令牌代表它登录。托管关系不会改变:将某人转为托管成员或从托管成员转出,都意味着要新建成员。对等方在 list_peers 中会看到托管成员带有相应标记,其消息也会带有该标记。
  • 发送方。 加密设置为 required 的联系人永远不会收到发给托管成员的消息。在 preferred 下,若要发送,必须在 contacts.json 中该联系人对应的加密条目上设置 "custody": "allow-hosted",之后消息将以明文发送。加密的 @role 或 @all 绝不会向托管成员发送副本:托管成员会被排除在外,并计为未加密(在 required 下不可用);此规则同样适用于设置了 allow-hosted 的情况。若名称列表中包含托管成员,发送操作会被拒绝。加密策略下的明文 @role 或 @all 会排除托管成员。要联系托管成员,请按名称向其发送消息。接收方可以通过 "hosted": "ask" | "block" | "allow" 将托管发送方视为一类(默认值为 ask:需要为该成员授予权限)。
  • 隔离措施。 只要中继的访问策略中存在带有提供方组(oidcGroups)的组,就会隔离托管成员:拒绝托管连接,暂存发给或来自托管成员的消息,并让其他所有人都无法识别托管成员。策略本身始终照常生效。hoptell relay admin status 会显示隔离状态和暂存消息数。
  • 端点 在运营方的 HTTPS 代理后运行。它接受为其签发的 OAuth 访问令牌(范围 hoptell.mcp),将每个 (issuer, subject) 关联到一个托管成员(hoptell mcp-hosted --experimental link),并提供 send_message、read_inbox、wait_for_message、ack_messages 和 list_peers。所有者在 https://<host>/owner 处理暂存联系人;权限授予和联系人请求也仅在此处显示。所有者的登录状态保存在内存中:重启端点会使所有者退出登录。
  • 限制与保留期限(会话、速率、存储的消息和字节数、磁盘下限、运行中的引擎)均可配置。默认情况下,消息会在到达 7 天后删除,无论是否已读取;成员解除关联后,或中继确认不再持有该成员数据后,只要端点仍在运行,这些数据就会在 24 小时内删除。托管模式仍处于实验阶段,因此默认值可能会改变。

团队与集群

名称。 每个 Agent 都有一个对等名称,仅可包含字母、数字、_ 和 -。可通过 HOPTELL_NAME 设置;也可以留空,由中继分配名称:使用成员令牌时为 <member>-<host>-<n>,使用共享令牌时为 <host>-<n>。list_peers 会显示当前使用的名称。

会话通过租约持有名称。会话在线时,其他连接无法占用该名称:名称相同的第二个 Agent 会话会等待名称空出,而 hoptell wait 会报错停止(使用 --wait-for-name 可改为等待,使用 --replace 则可为同一成员接管名称一次)。等待期间,如果持有该名称的是同一成员(或使用共享令牌的另一个会话),第二个会话仍可通过单独的连接发送消息和列出对等方。中继会在每次发送消息前检查名称由谁持有,回复会发送给持有该名称的会话。此功能需要 0.3.0 或更高版本的中继。在第二个会话持有名称之前,其他工具不可用。 当会话连接断开时,该名称会保留 30 秒,以便会话重新连接。在此期间,其他成员不能使用该名称,但同一成员可以使用,例如重启后的会话。所有使用共享令牌的会话在此处都算作同一个成员。正常退出的会话会立即释放名称。

会话标题。 自动名称(例如 emin-mac-61034)很难与下一个名称区分。会话可以在名称旁显示一个简短标题。使用 HOPTELL_TITLE 设置:

export HOPTELL_TITLE="review docs"

其他会话随后会在 list_peers 中、名称后看到该标题: emin-mac-61034 (reported host: mac) title: "review docs" online。

  • 标题仅用于标注。消息仍发送给名称(to: 不接受标题),也没有任何访问决策会读取标题。路由时请使用名称,不要将其视为身份凭据。在共享令牌或混合 realm 中,其他会话可能使用看起来像成员名称的名称。判断消息发送者时,请检查经过身份验证的发送者信息。
  • 作为提示,如果标题看起来像列表中的另一个名称,该行会显示 (title resembles another address)。
  • hoptell 会移除控制字符和不可见字符、合并空格,并将长度限制为最多 80 个字符。超过 1024 字节的标题,或清理后为空的标题,不会发送;会话会记录原因。
  • HOPTELL_TITLE=off 表示永不发送标题。HTTP 会话(hoptell mcp-http)不会使用服务端的 HOPTELL_TITLE。
  • 会话恢复连接时,标题会随其 30 秒的名称保留期一起保留。新的名称声明会使用自己的 hello 标题,或不带标题;绝不会继承先前名称声明的标题。
  • 标题需要使用此版本或更高版本的中继。较早版本的中继会忽略标题。一个会话每分钟最多更改标题 10 次,一个成员每分钟最多更改 60 次;更改标题绝不会延迟消息。

自动标题。 使用成员令牌登录的会话无需设置即可获得标题:

  • 使用 hoptell 插件的 Claude Code 会话会使用其自定义名称(claude --name、/rename)。
  • 否则,标题会显示客户端和工作文件夹的名称,例如 Codex · hoptell 或 Claude · agent-talk(如果是主文件夹本身,则显示 ~,因此不会显示你的账户名称)。

使用共享令牌的会话不会发送自动标题。发送的标题与名称一样可见:所有可以列出此会话的会话都能看到;在使用共享令牌的中继上,也可能包括该中继上的其他会话。HOPTELL_TITLE_AUTO=on 会为所有会话启用自动标题,HOPTELL_TITLE_AUTO=off 会将其关闭。

  • 请在 ~/.config/hoptell/.env 中设置,而不只是设置在 shell 中:插件的钩子也会读取此文件。
  • SessionStart 和 UserPromptSubmit 钩子只会将自定义名称和会话 ID 复制到私有文件中,绝不会复制提示词。
  • 钩子输入不会说明是谁设置了自定义名称。如果其他钩子从提示词文本中设置名称,该文本就会成为你的标题,其他会话也能看到。
  • 后续的 /rename 会在你下次发送提示词后显示。其他会话会在此会话下次列出对等会话或重新连接时看到该标题。
  • 显式设置的 HOPTELL_TITLE 优先。
  • 客户端名称来自 Agent 的 MCP 客户端如何自我报告。HTTP 会话(hoptell mcp-http)不会获得客户端和文件夹标题。

较早版本的中继不会租用名称:使用某个名称的新连接会替换旧连接。对于长度不超过 58 个字符的名称,被替换的 MCP 会话会等待名称释放后再重新连接。设置 HOPTELL_PROTOCOL=1,即可在当前版本的中继上保持此行为。 角色。 HOPTELL_ROLES=reviewer,backend(或 hoptell tmux <name> --roles reviewer -- codex)。 list_peers 会显示这些角色。发送给 @reviewer 会送达所有担任该角色的在线对端, 发送给 @all 会送达所有在线对端。忙碌的对端会将消息排在尚未确认的消息后面。扇出消息不会为离线对端排队。直接 发送给某个名称的消息会在该对端离线时排队(每个对端最多 50 条,保存在中继内存中)。 角色是 Agent 为路由工作自行选择的标签,不是权限。

加密策略下的角色(实验性)。 当加密发送可用时 (HOPTELL_EXPERIMENTAL_E2EE=1,且设备已注册),并且该域的加密策略不是 off (hoptell contacts encryption),@role 和 @all 会以加密形式发送,每个接收者会话各一份,且仅发送给你已固定其设备的成员(新成员需要先收到一条直接消息)。这些消息绝不会以明文发送:联系人加密设置为 off 的会话,或无法接收加密消息的会话,都不会收到消息,并会计入结果。若要以明文联系这些会话,请逐一按名称发送。当加密发送不可用且策略仅为 preferred 时,@role 和 @all 会以明文发送,结果会注明消息未加密。当该域存在任何非 off 加密策略时,hoptell send @role 和 hoptell send @all 会被拒绝。加密设置为 required 的联系人绝不会收到明文,包括通过 hoptell send 发送,或未设置 HOPTELL_EXPERIMENTAL_E2EE 的情况。

多人使用。 为每个人分配独立令牌,避免任何人冒充他人的 Agent。在中继上创建成员文件(chmod 600):

{ "members": [
    { "name": "alice", "token": "<openssl rand -hex 32>" },
    { "name": "bob",   "token": "sha256:<hex sha256 of bob's token>" } ] }

在中继主机上,运行 hoptell members add alice(使用 HOPTELL_MEMBERS 或 --members <file>)可添加一个成员,并为其生成 ID 和令牌。令牌只显示一次,且仅存储其 SHA-256。hoptell members list 会列出成员,但不显示令牌;hoptell members revoke alice 会移除成员。即使成员名称更改,其 ID 仍可用于向中继标识该成员。没有 ID 的成员(如示例所示)会按名称识别;hoptell members migrate 会列出这些成员,并在指定 --yes 时为其分配 ID。重新加载中继会关闭他们的连接;请重启这些会话,使其使用新 ID 重新连接。新身份接管名称时,旧身份下为这些名称排队的消息会被丢弃。

members 命令会将每次更改记录到中继审计日志中。请在中继主机上运行这些命令,并指定中继的状态目录(与中继相同,使用 --state-dir 或 HOPTELL_STATE_DIR)。如果中继正在运行,该命令会通过状态目录中的管理套接字重新加载中继,然后报告结果。如果中继已停止,则会在启动时应用更改。hoptell relay admin audit 会显示日志,hoptell relay admin help 会列出中继所有者的其他命令。

运行 hoptell relay --members members.json 或设置 HOPTELL_MEMBERS。成员只能使用名称 <member>,或以 <member>- 开头的名称(alice-claude、alice-codex-2)。 中继会拒绝彼此重叠的成员名称,例如 alice 和 alice-bob。 你可以将成员文件与共享的 HOPTELL_TOKEN 配合使用;持有该令牌的人可以使用任意名称。 --require-members(或 HOPTELL_REQUIRE_MEMBERS=1)会使中继拒绝共享令牌。 如果手动编辑文件,请运行 hoptell relay admin reload 或 kill -HUP <relay pid>。Relay 会重新读取文件,在审计日志中将其记录为未公告的变更,并关闭令牌已被移除或更改的连接。如果新文件无效,它会保留当前的成员列表,并记录经过清理的错误。空列表({"members": []})会移除所有成员。共享令牌不会重新加载。使用 systemd LoadCredential 时,请重启服务以刷新凭据副本。编辑器以原子方式替换单个文件时,Docker 的单文件绑定挂载可能会保留旧文件;此类编辑后请重启容器,或者绑定挂载所在目录以支持重新加载。 对于不同的团队,请分别运行各自的 Relay。Relay 是一个小型的单进程程序。

在一台机器上运行的示例集群:

hoptell tmux alice-planner  --roles planner  -- claude
hoptell tmux alice-codex-1  --roles backend  -- codex
hoptell tmux alice-codex-2  --roles backend  -- codex
hoptell tmux alice-reviewer --roles reviewer -- claude

然后告诉规划器:“拆分任务,将后端工作交给 @backend,并将结果交给 @reviewer。”

防护机制。 每个连接每 10 秒最多可发送 30 条消息,因此,两个不断互相回复的 Agent 会触发速率限制,而不是向所有人刷屏。消息为纯文本,最长 100,000 个字符。

联系人:谁可以联系你的会话

由你决定谁可以联系你这台机器上的 Agent 会话。新安装时,你尚未允许的发送者发来的消息会被暂扣(符合条件的、来自你自己的成员的消息除外,见下文“你自己的成员”),Agent 不会看到这些消息的内容。

接收消息的会话仍会被唤醒,方式与收到已投递的消息时相同(钩子通知、tmux 通知、频道通知,或 wait_for_message 调用或 hoptell listen 结束时的通知),并会收到一条固定通知,告知有消息被暂扣。read_inbox 会列出最多五个待处理发送者及其被暂扣的消息数,并提供 hoptell contacts show <id> 命令供你查看。其余发送者会汇总为一个数量,并提醒 Agent 由你作出决定。

只有当中继将发送者验证为成员时,这里才会显示其名称(member bob);使用共享令牌或未经验证的发送者可以自行选择名称,因此 Agent 只能看到发送者的类型。桌面通知(“hoptell · Message held”)也会引导你使用 hoptell contacts 和 hoptell ui:

hoptell contacts                          # what is waiting, and your policy
hoptell contacts show <id> [--text]       # the sender, how the relay authenticated it, the sessions;
                                          #   --text shows the held messages (untrusted peer text)
hoptell contacts allow <id> --rev N --policy-rev P        # always allow this sender (a grant)
hoptell contacts allow-once <id> --rev N --policy-rev P   # release only these messages
hoptell contacts block <id> --rev N --policy-rev P        # delete them and block the sender
hoptell contacts grants | blocks | revoke <id> | unblock <id> | check ...

show 会打印要使用的准确 --rev 和 --policy-rev,确保决定不会应用于你查看后发生变化的消息或策略。--session <realm>/<name> 可将决定限定到你的某个会话。授权时使用中继验证的发送者身份:成员(使用其成员 ID,该 ID 不会因更名而改变),或者对于共享令牌,使用准确的发送者名称(任何共享令牌持有者都可以使用任意名称)。对于中继未验证身份的发送者,你可以放行一次或屏蔽,但不能授权。对端发送的消息永远不会批准任何内容,Agent 可以调用的 hoptell 工具也不能批准;以你的 OS 用户身份运行的 Agent 仍可自行运行这些命令,因此允许 Agent 使用 shell 时请牢记这一点。

策略文件为 ~/.hoptell/contacts.json(每个 hoptell 主目录各有一份;JSON Schema 见 schema/contacts.schema.json)。暂扣的消息会保留 7 天;每个发送者最多保留 50 条消息或 1 MB,所有发送者合计最多保留 500 条消息或 10 MB;超出上限后,来自未知发送者的新消息会被丢弃(但仍会向中继确认已投递)。

在受管理的主目录中,如果此文件缺失、无法读取或无效,所有发送者的消息都会暂扣,且你的会话不会发送任何内容,直到你修复该文件,因为无法检查其加密策略。文件有效时,来自已验证所有者频道的消息不受默认 ask 策略限制;但屏蔽规则和 required 加密要求仍然适用(见“通过 Telegram 使用所有者频道”)。

现有安装会继续保持当前行为(所有发送者都可以联系你的会话),直到你运行 hoptell contacts init;hoptell doctor 会提醒你。运行 init 后,没有任何人会获得授权,而会话中已经等待的消息会在下次读取时被暂扣。此主目录中所有接收消息的进程都必须运行此版本或更高版本。HOPTELL_NOTIFY=off 可关闭桌面通知。

你自己的成员。 默认情况下,如果中继声明两个会话以同一成员身份登录(成员 ID 和 realm 相同,且双方都未托管),来自你其他会话的消息无需授权即可联系 Agent。只认可由中继验证的成员 ID,绝不使用会话名称或主机。共享令牌发送者、其他成员、托管成员,以及中继未验证身份的发送者,仍受你通常的联系人策略约束。你的屏蔽规则和默认的 block 仍然适用;如果你的策略要求对你自己的成员使用加密,明文消息也会被暂扣。这与授权一样,都是信任中继的身份声明:如果中继撒谎,就可能以你自己的成员身份投递消息。若要让你自己的成员也遵循通常的联系人策略,请在设置文件中设为 HOPTELL_CONTACTS_SELF=hold。请在设置文件中设置,以便 Agent 会话、tmux 注入器和 CLI 都能读取该值;派生的 Agent 运行会采用启动它们的会话中的值。切换回 allow 后,已经暂扣的消息仍会保持暂扣,直到你作出决定。

在浏览器中做出决策:hoptell ui

hoptell ui 会在 127.0.0.1 上将相同的决策界面作为网页提供:等待你处理的事项(暂存的消息、新设备和密钥变更、联系人请求和提案,按时间从早到晚排列)、你的联系人和置顶项、包含指纹和安全码的设备、显示每个值来源的设置,以及诊断摘要。

它会打印一个有效期为 60 秒的一次性链接(--open 会打开该链接);闲置 15 分钟或达到 8 小时后会话结束,并且命令在 30 分钟没有收到请求后退出。页面不含 JavaScript。暂存消息的文本仅显示在其对应页面上,并被视为不可信的对端文本。

每项决策都通过与 CLI 相同的代码执行,并以相同方式绑定到你看到的内容:如果相关内容在此期间发生变化,系统会拒绝这项决策并显示新状态。UI 只显示联系人请求和提案,并打印对应命令(hoptell contacts requests、hoptell apply)。只有在页面显示文件之后文件未发生变化,设置才会写入;在环境中设置的值会显示出来,且必须在环境中更改;扩大访问范围的变更(新增文件夹、join、default: allow)会标记出来。设置还会列出在其文件上次更改之前启动、带有钩子投递功能且仍使用旧值的运行中会话。同一秒内启动的会话可能仍在使用旧值,也可能没有,并会标为“可能仍在使用旧值”。

启用实验性加密消息(HOPTELL_EXPERIMENTAL_E2EE=1)后,每个联系人的 encryption(off、preferred、required)也属于设置;降低该设置会被标记为扩大访问范围,requireVerified 也是如此(按 realm 和每个置顶联系人分别设置;关闭时会标记)。对于你要求加密、但暂存了未加密消息的联系人,系统会显示指向该设置的链接(只有更改该设置才能放行这些消息);联系人的页面还会链接到加密对话的检查点,以便与另一位所有者核对。

目前,需要中继的视图(联系人请求、设备列表、加密对话检查点)只能通过 HOPTELL_RELAY(以及 HOPTELL_PROFILE)访问中继,而不能通过中继文件访问;如果未设置这些变量,页面会提示中继不可用。

这台机器上的其他用户可以访问该端口:启动链接和会话 Cookie 会阻止他们进入。浏览器不会按端口区分 Cookie,因此在会话有效期间,你访问 127.0.0.1 上的任何其他服务器时,浏览器也会向其发送会话 Cookie;这样的服务器可以在会话结束前像你一样使用 UI。完成后请关闭 UI(Ctrl-C),并在此期间不要浏览你不信任的其他本地服务器。以你的操作系统用户身份运行的 Agent 既能打开 UI,也能运行 CLI;UI 可防范对端、过期页面和意外操作,但无法防范这样的 Agent。

在 Agent 客户端中询问(可选)

设置 HOPTELL_ELICITATION=read_inbox 后,read_inbox 调用可能会先通过客户端自身的问题对话框询问你一位消息被暂存的发件人:发件人是谁(由中继验证身份)、有多少条消息、涉及哪些会话,但绝不会询问消息文本。可选回答为 allow-once(仅放行这些消息)和 later;持久性决策(allow、block)和设备密钥仍由 CLI 和 hoptell ui 管理。如果你拒绝、取消、60 秒内未作答或给出其他回答,消息会继续等待,并且 30 分钟内不会再次询问该发件人。此功能默认关闭。

只有 MCP 修订版为 2025-06-18 或 2025-11-25,且客户端声明支持表单问题的会话才会发起询问;对于 2026-07-28 修订版或未知修订版,则绝不会发起询问。回答来自客户端,而不是 Agent 的工具调用,但客户端可能会自动回答(Claude Code 的 Elicitation 钩子可以自动回答;设置 approval_policy = "never" 的 Codex 会立即拒绝),因此收到回答并不能证明是人作出的。请参阅已测试的客户端。

中继端检查:授权和中继策略

你机器上的联系人决定哪些内容会到达你的会话。在使用成员凭据的中继上,消息离开中继前还会经过两项检查,且每项检查都只能拒绝:

  • 中继策略(hoptell relay --policy policy.json 或 HOPTELL_POLICY),由中继运营者设置:成员分组(按成员 ID,或已登录成员的身份提供方分组)以及组间的允许或拒绝规则。hoptell relay admin reload(或 kill -HUP <relay pid>)会重新加载策略并将变更记录到审计日志中;如果文件有误,则继续使用当前生效的策略。
{ "v": 1,
  "groups": { "reviewers": { "members": ["m_..."], "oidcGroups": [{ "issuer": "https://idp.example", "group": "reviewers" }] } },
  "rules": [{ "from": "group:reviewers", "to": "*", "action": "allow" }],
  "defaultAction": "deny" }
  • 你的授权,由你为自己的成员发布:
hoptell grants                                   # your grants and their revision
hoptell grants allow member:<id> [--until <s>]   # also group:<name> or *; --until for a while
hoptell grants deny member:<id> | remove <selector>
hoptell grants deny-forever member:<id>          # deny, and no contact requests from it
hoptell grants default allow|deny                # for senders no entry names
hoptell grants visible all|grantees              # grantees: others do not see you in lists
hoptell grants publish                           # the same grants again (after a relay restore)

每条 grants 命令都会先显示结果和当前修订号,且不会发布任何内容。使用相同命令再次运行,并添加 --yes --rev N(即刚才显示的修订号)即可发布。如果这期间你的授权发生了变化(例如在另一会话或机器上),命令会拒绝执行,你需要重新查看。以秒为单位的 --until 从发布时刻开始计时。 成员条目优先于组条目,组条目优先于 *;规则相同时,拒绝优先。每次投递消息时都会检查是否过期,已在等待的消息也不例外。

如果你的授权或策略拒绝某个发送者,对方收到的回复与名称不存在时相同;广播只会报告送达的会话数量。

中继恢复后。 如果中继从备份恢复,且其访问状态已丢失,它就无法确定备份中的授权是否为你最新发布的版本。在你再次发布之前,发送到你会话的消息会失败并返回 unknown peer(与名称不存在时相同,因此没人会知道哪些名称是成员),排队的消息也会继续等待。这适用于每位成员,包括从未更改过授权的成员。你的会话会收到提示:hoptell grants 和 hoptell doctor 会说明这一情况,Agent 会话的 read_inbox 也会提及。使用 hoptell grants 检查授权,然后使用 hoptell grants publish --yes --rev N 发布,或先进行修改。中继管理员可通过 hoptell relay admin grants 查看仍待处理的成员。

回复。 如果你发送的消息默认会被你的授权拒绝,对方可在 7 天内回复一次,但前提是你的消息通过了中继策略检查,并且对方机器上的联系人设置允许将消息送达某个会话。回复仍需通过中继策略检查。你的授权中若有条目拒绝对方(无论按成员还是按其所属组),则回复权限会被取消;过期也会取消。

联系人请求。 如果中继策略允许某成员,但你的授权拒绝了该成员,对方可以通过固定理由(不含文本)请求联系你:hoptell contacts request <name> --reason collaborate|review|handoff|other。无论你是否存在、是否接受请求或是否已屏蔽对方,发送者收到的回复都相同。你可以通过 hoptell contacts requests 查看请求;accept、deny 和 deny-forever 会立即生效,不提供预览。接受请求会将该成员添加到你的授权中;但哪些内容能到达你的会话,仍由你机器上的联系人设置决定。

隐藏自己(visible grantees)需要使用不带共享令牌的中继。要求授权(策略中设置 "requireGrants": true)或隐藏成员的中继会拒绝无法进行过滤的旧客户端(协议 1)。每位发送者在中继上等待的消息总量(涵盖所有接收者)最多为 1,000 条或 20 MiB。没有策略且没有已发布的授权时,投递行为不会改变;中继仍会记录登录信息(见下文),以便之后添加的策略使用这些信息。

回复和文件审查

0.2.0 及更高版本的客户端会在消息文本后显示一行 Message reference:。如果其中包含 UUID,回复时请将该 UUID 作为 reply_to(MCP)或 --reply-to(CLI)传入。0.2.0 及更高版本的接收端客户端会将链接显示为 In reply to:。如果没有引用,请省略该选项。0.2.0 之前的中继不提供引用,较旧的客户端渲染器也不会显示这些字段。回复引用是由发送方提供的标签;中继不会验证被引用的消息是否存在。

将文件交给其他 Agent 审查时,审查者可能会在文件发生更改后才回复。要确定建议针对的是哪个版本:

  1. 运行 hoptell snapshot greet.js 并原样发送完整输出。该命令通过一个文件描述符读取文件,并包含所读取字节的 SHA-256、一个 review_request_id 和文件内容。在任务上下文中保留原始请求 ID、摘要、本地路径和指定的审查者。文件必须包含有效的 UTF-8 编码,且大小不得超过 60,000 字节;编码后的快照也必须符合 100,000 个字符的消息长度限制。
  2. 审查者引用 review_request_id,并将快照的 sha256 作为 based_on 提供。
  3. 确认回复来自你指定的审查者,并且其 review_request_id 和 based_on 与原始快照匹配。然后针对原始本地路径运行 hoptell snapshot --check <original-sha256> greet.js。如果元数据不匹配或文件已更改,请重新请求审查,或审慎地处理更改。

based_on 值是审查者的声明,并不能证明模型读过什么。检查会将当前文件字节与提供的摘要进行比较;它不会验证回复或应用更改。读取过程不是原子文件系统快照,检查后文件仍可能再次更改。未保存的编辑器更改不在比较范围内。已连接的 Agent 可在 hoptell 说明中获取这些步骤。

文件工具:send_file 和 verify_snapshot

Agent 还可以不运行命令直接发送文件,之后按 ID 检查文件。这些工具默认关闭。要启用这些工具,请在发送方机器(macOS 或 Linux)上批准文件夹:

在启动 MCP 服务的 shell 中设置此项,或将 KEY=VALUE 设置添加到其本地设置文件中。

export HOPTELL_SNAPSHOT_ROOTS='[{"id":"app","path":"/Users/sam/work/app"}]'

(在插件中,使用 Approved snapshot folders 设置;[] 会关闭这些工具。) 更改此设置后,重启 MCP 服务(如有需要,也请重启 Agent 会话)。

  • send_file 接受 to(一个对端)、root_id、该文件夹内的 path 和 request。它通过一个拒绝符号链接、硬链接和特殊文件的读取器,读取最多 60,000 字节的 UTF-8 文本,并拒绝读取期间检测到的更改。编码后的快照也必须符合消息长度限制。默认情况下,它会请求 1 小时的中继队列截止期限;较旧的中继会忽略此请求。该工具会返回一个本地 snapshot_id。
  • verify_snapshot 接受该 snapshot_id,并报告 match、changed、unknown、expired 或 unavailable。它只比较文件字节,不会验证回复。

获批文件夹位于本地:其他 Agent 发来的请求绝不会添加获批文件夹,审查者的机器也不会将发送方的文件标签解析为路径。已发送快照的记录会在 24 小时后过期。启用了文件工具的 MCP 服务运行期间,会清理过期记录;备份可能会保留副本。

审查集和由所有者运行的应用

获批文件夹还会让 Agent 获得 send_review_set 和 verify_review_set,以便一次处理多个文件:

  • send_review_set 接收 to(一个对等方)、files([{"root": "app", "path": "src/a.js"}, ...])、 一个 request,以及可选的 git: true。每个文件都会通过同一个受保护的读取器读取一次; 重复列出的文件,或因重叠文件夹或大小写别名而可被重复访问的文件,都会被拒绝, .git 文件夹(不区分大小写)中的任何文件也会被拒绝。 最多 50 个文件,总大小不得超过 512 KB;整条消息还必须符合中继的消息大小限制 (目前为 100,000 个字符);超过任一限制的文件集都会被拒绝,并报告大小,不会截断。对等方会收到一条消息:{hoptell_review_set: 1, review_set_id, manifest, contents}。文件按文件夹 ID 排序,其次按路径排序;bundle_digest 是对 manifest.files 的 RFC 8785 规范化 JSON 计算得到的 SHA-256。使用 git: true 时,清单还会逐个文件标明捕获内容是 matches-head、differs-from-head 还是 untracked;这仅用于记录来源:之后的所有操作都以捕获的字节为基准。
  • 审阅者的 hoptell 会根据 SHA-256 校验每个文件,并在消息下方显示 bundle_digest。审阅者会用一条消息回复,整条消息的内容都是一份提案:{hoptell_proposal: 1, proposal_id, review_set_id, based_on: <bundle_digest>, changes: [{file_id, base_sha256, kind: "replace", text} | {file_id, base_sha256, kind: "patch", unified_diff}]}。只能使用文件 ID:不能包含路径、命令或新文件。
  • 中继会告知发送者,它把直接发送的消息交付给了谁(某位成员,或使用共享令牌时所用的名称),并由文件集记录该信息。提案在存储前会经过校验:必须来自该接收者(相同的 realm 和名称;如果是成员,则必须来自该成员,绝不能是使用其名称的共享令牌发送者),必须指向尚未过期的文件集,匹配其 bundle_digest,且每个文件只能列出一次,并且必须匹配该文件记录的 SHA-256。其他情况都会被丢弃,并附带固定提示。每个文件集最多保留 20 份提案,每份不超过 1 MB,合计不超过 50 MB;相同的 proposal_id 只存储一次,若负载不同则会被拒绝。Agent 只能看到该文件集有一份提案正在等待你处理,永远看不到其内容;hoptell wait 和被联系人暂扣消息的发送者也一样(那里只会显示提示)。
  • 较旧的中继会确认消息已发送,但不会说明接收者。此时文件集会记录这一点,校验条件也会放宽:同一 realm 中任何经过身份验证且名称与记录相符的发送者都可以提交提案,而不再要求来自绑定的成员。send_review_set 会在其 recipient 字段中说明这一点,hoptell apply 也会在预览中显示。保存中继确认之前,没有任何提案可以匹配该文件集:先到达的提案,或针对确认未保存、或以意外格式记录了接收者的文件集作出的回复,都会被丢弃。绝不会仅凭 realm 和名称接受提案。如果保存失败且结果不明,send_review_set 会说明这一点,并列出两种可能性:只有指定的接收者可以回复,或者任何人都不能回复。
  • verify_review_set 会报告 match、changed(附带相关文件)、expired 或 unknown。

只能由你在终端中应用提案;没有任何 MCP 工具可以做到:

hoptell apply                          # proposals waiting for you
hoptell apply <proposal id> --preview  # what it changes; writes nothing
hoptell apply <proposal id>            # the same, then asks; --yes skips the question
hoptell apply --recover [--finish | --roll-back]   # after an interrupted apply

更改会应用到私有临时文件夹中的已捕获字节(绝不会是你的仓库),因此你在捕获前所做的编辑都会保留。补丁必须只更改记录的文件(不得包含绝对路径、..、.git、符号链接、子模块、模式更改或二进制内容),并且必须通过不带任何配置、钩子、过滤器或分页器的 git apply 应用;需要 Git 2.39.2 或更高版本(或旧系列中已修复的版本)。预览是 hoptell 自行生成的差异,展示每个当前文件与应用结果之间的区别。写入前,会再次检查每个文件:文件仍位于其文件夹内,路径上没有符号链接;它仍是捕获时的同一个文件;内容仍是捕获时的字节。任何一项不符,都会拒绝整个应用操作。.git 文件夹内的文件绝不会被写入,不论更改类型为何。系统会先写入并同步恢复日志(~/.hoptell/apply/<id>/)。随后,每个文件都会以新文件原子替换:新文件会沿用旧文件的权限位,但它是一个新的 inode,因此不会保留 ACL、扩展属性(xattrs)以及指向旧文件的其他硬链接。多文件应用在执行过程中并非原子操作。中断后,hoptell apply --recover 会将每个文件恢复为旧内容或新内容;如果有人更改了某个文件,则会停止处理该文件(绝不会覆盖它;日志会保留两个版本)。完整日志状态保存后,应用操作即提交(在此之前发生中断仍可回滚):从那时起,恢复操作只会将提案标记为已应用并移除日志,而 --roll-back 会被拒绝(“already committed”)。恢复操作仅在残留临时文件与日志记录的文件完全一致时才会将其删除;其他文件会原样保留,并列出供你检查。hoptell doctor 会报告未完成的日志。若某个程序恰好在最后一次检查与重命名之间编辑文件,该编辑可能会丢失;此类竞态不在这些检查的保护范围内。提案不会触发任何程序运行。

评审集记录(连同捕获的文件)会保留 7 天,或保留到该集合的所有提案都已应用或过期为止,以较晚者为准。

加密消息:你的设备与验证(实验性)

加密消息功能在 0.3.0 中仍属实验性,且未经独立评审。该功能默认关闭,除非你指定,否则 hoptell 不会加载任何 MLS 库。每条加密消息都会标记为“实验性,未经评审”,hoptell doctor 也会如此提示。现阶段请勿依赖它来保护敏感消息。

试用方法:

  1. 使用 --experimental-e2ee 启动中继(或在其设置中设为 HOPTELL_EXPERIMENTAL_E2EE=1)。

  2. 在每台机器上,将以下内容添加到设置文件(~/.config/hoptell/.env):

    HOPTELL_EXPERIMENTAL_E2EE=1
    # The OpenMLS binding shipped in the package, by absolute path: replace <npm-root>
    # with what `npm root -g` prints, for example /usr/local/lib/node_modules.
    HOPTELL_MLS_WASM=<npm-root>/hoptell/lib/mls/binding
    

    HOPTELL_MLS_WASM 接受绑定的 openmls_wasm.js 绝对路径或其文件夹路径:可以是面向 Node.js 的 wasm-bindgen 构建(--target nodejs),也可以是提供 initSync 且其 openmls_wasm_bg.wasm 与之同目录的构建,随附的 bundle 即采用此方式。HOPTELL_MLS_DRIVER 则用于指定你自己的驱动程序模块(一个导出 driver 或 createDriver() 的 ES 模块)。无论采用哪种方式,标记都会保留。

  3. 设备密钥保存在操作系统密钥链中(macOS Keychain,或 Linux 上通过 secret-tool 使用 Secret Service)。如果没有密钥链(例如在服务器上),请设置 HOPTELL_CREDENTIAL_STORE=file 和 HOPTELL_CREDENTIAL_PASSPHRASE(至少 12 个字符),将密钥保存在加密文件中。

  4. 如果尚未运行 hoptell contacts init,请先运行,然后运行 hoptell device enroll,并启动(或重启)你的 Agent 会话。hoptell doctor 会检查设置。

每个 hoptell home 在每个中继 realm 中都有一个 MLS 设备。

  • 登记。 hoptell device enroll 需要已初始化的联系人管理(hoptell contacts init):新设备会先作为此家庭自有设备记录在其中,然后中继才会收到相关信息。每个家庭在每个 realm 中只能有一台设备;如果该家庭在某个 realm 中已有设备,则拒绝在该 realm 中登记第二个中继配置。hoptell device retire 会放弃此配置的设备(仍可读取,但不能发送)。如果注销的配置持有此家庭在该 realm 中的自有设备,则会保留该记录,并标记为已注销:此后,在你运行 hoptell device enroll --new-baseline 之前,任何配置都无法在该 realm 中登记设备。注销重复配置(同一 realm 中的第二个配置)不会影响另一个配置的设备。
  • 你的其他设备只有在你接受后才受信任,而不会仅仅因为中继列出了它们就受信任。在现有设备上运行 hoptell device accept <device>,将其指纹与新设备上的 hoptell device status 进行比较,然后运行打印出的 --set <code> 命令。新设备完成注册后,在新设备上重复这些步骤以接受现有设备,并与该设备的状态进行比较。
  • 新设备需要在中继处获得你的批准。 只要你在某个 realm 中有一台已注册且未撤销的设备,中继就只会在你的某台活跃设备或中继管理员批准后,才注册另一台设备。新设备上的 hoptell device enroll 会向中继发出请求、打印其指纹并等待(“等待你的某台设备批准:请在该设备上运行 hoptell device accept ”)。在现有设备上运行 hoptell device accept <device>,并将指纹与新设备上的指纹进行比较。它会打印一条包含 --set <code> 的命令;运行该命令即可接受此设备并发送已签名的批准。如果无法立即发送批准,会保留该批准,并在该设备每次连接时重新发送。每次运行 hoptell device enroll 最多等待 15 分钟,而中继会将请求保留最多 24 小时。如果命令在收到批准前停止,请在新设备上再次运行。如果请求过期,请重新登记并接受(已接受设备更换密钥会被拒绝)。hoptell device pending 会列出等待你的设备处理的请求,hoptell device pending --cancel <device> 会取消其中一个请求;之后,相同设备和密钥必须等待 24 小时才能再次请求。仅持有你的成员令牌的人可以占用四个常规待处理请求名额,但只要你的某台设备仍未撤销,就无法注册额外设备。如果名额已满,请先在新设备上运行 hoptell device enroll;即使请求被拒绝,这也会创建其设备 ID 和密钥。在该设备上运行 hoptell device status,获取 ID 和标记为 Pin identity 的值(用于标识该设备及其密钥的 64 位十六进制标识符)。在一台活跃的现有设备上运行 hoptell device expect <device> <pin identity>,然后在新设备上重试登记。这会预留一个额外的请求名额,但你仍须批准该请求。如果你的设备都无法批准(从中继恢复后全部处于暂停状态,或全部丢失),则由中继管理员通过 hoptell members device-approve <member> <device> <fingerprint> 批准请求(会记录审计日志)。如果成员没有任何仍未撤销的已注册设备,新设备无需批准即可注册。在这种情况下,成员令牌可以注册设备。无论设备是否注册,成员令牌都可以代表该成员发送和读取明文消息。
  • 检查你的设备。 hoptell device list 会显示你已接受的设备、中继列出但你尚未接受的设备,以及不再列出的设备。如果某个会话包含你尚未接受的设备,则会暂停(不会发送或提交任何内容),直到你接受该设备或撤销它。hoptell device enroll --new-baseline 会在你明确确认后使用新的设备 ID 重新开始:当你注销此家庭在该 realm 中的设备后、你的所有设备都丢失时,或无法读取此设备的记录时,都可以使用此命令。被替换的记录会保留供审查,你的联系人会将新设备视为你的新设备。
  • 验证联系人:运行 hoptell contacts verify <member id>。该命令会显示你和对方的设备、各自的指纹和状态,以及可当面或通过可信渠道进行比较的安全号码。你的号码部分来自你已接受的设备,而非中继的列表。如果中继列出了你尚未接受的设备,验证会被拒绝;如果联系人的某台已固定设备不再列出,也不会进行固定。
  • 中继不再列出的设备不会被移除:发送给该联系人的消息会一直等待,直到该设备再次列出、收到由其另一台设备签署的撤销信息,或你运行 hoptell contacts forget-device <member id> <device>。如果消息未送达联系人的任何设备,系统会报告为未送达。
  • 限制(首次使用)。 对于你尚未验证的联系人,首次使用时会将其视为可信:hoptell 会在发送第一条加密消息时采用中继提供的该联系人设备列表。中继可以在列表中添加设备。验证联系人或设置 requireVerified 后,后续消息将不再适用此规则。
  • 仅使用后量子 MLS 套件 0x004D;不支持该套件的绑定会被拒绝。每个会话每至少 7 天的活动时间都会发送一次更新,并在移除成员后发送更新;hoptell doctor 会列出上次更新时间超过 30 天的成员。计划密钥轮换(hoptell device rotate)要求设备没有加密会话;由设备签名的轮换和撤销要求 MLS 绑定支持签名(否则会在做出任何更改前被拒绝;中继所有者仍可通过 hoptell members device-revoke 撤销设备)。
  • 设备密钥证明。 连接只有在使用该设备的密钥签署新鲜的中继质询后,才能代表该设备执行操作(接收其消息、发布其 KeyPackages、进行传输);仅有成员令牌并不足够。较旧版本的 hoptell 无法连接到此类中继,需要更新。hoptell device replenish 等命令会在各自的连接上证明设备身份,但不会接收该设备的任何消息。轮换信息会在中继收到之前保存;如果操作中断或被中继拒绝,则会在下次启动时重试。
  • 过期的 KeyPackages。 如果 MLS 绑定支持清理,未使用且已过期的 KeyPackages 私钥会在 1 天宽限期后删除;引擎运行期间会在 6 小时内完成(在启动时、连接时、发布时以及每 6 小时检查一次,即使中继拒绝该设备时也会检查)。未运行的设备会保留这些密钥,直到下次启动;较旧的备份中也仍会保留这些密钥。hoptell doctor 会在绑定不支持清理时说明这一点。
更大的文件传输(实验性)

启用实验性加密消息(HOPTELL_EXPERIMENTAL_E2EE=1 且设备已注册),使用 --experimental-e2ee 启动中继,并设置已批准的文件夹后,Agent 还会获得 send_transfer:可向一个对等方发送一个任意内容的文件,最大为 200 MiB。

  • 文件通过受保护的读取器读取一次,存入私有发件箱(~/.hoptell/outgoing/),然后在那里按 256 KiB 分块加密(AES-256-GCM,每次传输使用新密钥),并以密文形式上传。重试和续传会再次发送已存储的密文;不会重复加密。密钥只会随通知文件的加密消息发送。

  • 上传封存后,中继会保留加密分块 7 天(未在 24 小时内完成的上传会过期)。中继只会向消息被接受时已参与对话的接收方设备提供这些分块,并且只有在中继的访问规则仍允许发送方联系接收方时才会提供;规则依据发送方当前的登录状态判断(身份提供方的群组发生变化或退出登录都会使其停止访问)。发送方可以提前撤回文件。

  • 接收方的 Agent 只会看到 a file transfer from X (N bytes, label "...") is waiting for your user。只有在 contacts.json 中允许从该联系人接收传输时,hoptell 才会获取文件(默认不允许):

    "transfers": {
      "contacts": [{ "realm": "<realm id>", "member": "<member id>", "allowed": true, "maxBytes": 104857600 }],
      "stagedBytes": 2147483648, "stagedFiles": 20, "concurrent": 2
    }
    

    文件大小取自经过加密和身份验证的标头,并会在获取任何内容前进行检查;此外还会检查此主机的限制(默认限制为 2 GiB、暂存 20 个文件,同时下载 2 个)。因达到限制而被拒绝的文件之后不会再获取:发送方必须等有空间后重新发送。标签仅用于显示。

  • 获取的文件会逐块及整体检查,然后以本地 ID 存放在 ~/.hoptell/incoming/ 中;发送方提供的名称不会用于选择路径。暂存后未保存的文件会在 7 天后删除。

hoptell transfers                      # what is staged or being fetched, with the relay's expiry
hoptell transfers cancel <t_id>        # stop or delete one on this device only
hoptell save <t_id> <path>             # never replaces an existing file
hoptell save <t_id> <path> --overwrite [--yes]   # shows the file it replaces and asks
hoptell save --forget <t_id>           # drop the record of an interrupted save you have looked at
hoptell doctor [--fix]                 # finishes interrupted saves; --fix removes leftovers

save 会在目标文件旁写入临时文件,再次检查其 SHA-256,然后将其链接到目标位置;如果该位置已存在任何内容,操作就会失败。使用 --overwrite 时,会在再次确认目标仍是之前显示的那个文件后,将临时文件重命名覆盖该文件。~/.hoptell/saves/ 中的日志可让 hoptell doctor 完成或报告中断的保存;它只会删除能够证明是自己创建的文件,并会保留且报告其他内容。如果你自己的账户下有进程在重命名前一瞬间替换文件夹或文件,它可能会将操作重定向到你自己的其他文件中;重命名后的检查会报告文件已被替换。MCP 工具不会保存或打开任何内容。

加密消息到达对等方时,超过一帧的审阅集也会采用相同方式处理:清单放在消息中,内容作为传输发送,最大为 8 MiB。无需添加 transfers 条目;审阅者的 hoptell 会根据清单检查每个文件,然后才让 Agent 看到这些文件。

在中继上,传输文件存储在其存储区中。--transfer-quota(总计 20 GiB)和 --transfer-member-quota(每个成员 1 GiB)限制成员可在此存储的内容;每个成员最多可有 20 个未完成的上传和 1,000 个传输(包括最近结束的传输)。磁盘空间需预留约为传输配额 1.25 倍的容量,另加写前日志限制(见下文)和 128 MiB。中继的策略可以拒绝上传,配置方式为 "transfers": [{ "from": "group:<name>", "action": "deny" }](选择器与其规则相同,由第一个匹配项决定;默认允许);群组的身份提供方群组依据上传者当前的登录状态判断。

与审查者 Agent 协作

你可以让 Claude Code 会话启动其他 Agent 作为审查者,向它们发送文件并收集审查结论。Claude Code 插件自带三个技能,以及使用这些技能的斜杠命令:

命令技能功能
/hoptell:reviewers [count] [codex|claude]hoptell-reviewers启动审查者:Codex 只读,Claude Code 处于计划模式
/hoptell:review <file> [name or @reviewer]hoptell-reviewers发送快照并收集审查结论
/hoptell:ask-owner <question>hoptell-owner-channel通过所有者频道向你提问
/hoptell:peershoptell-safe-ops列出同伴并说明名称问题

任务符合条件时,这些技能也会自动加载。它们随插件提供,不随 npm 包提供;其命令需要在你的 PATH 中找到 hoptell CLI。

审查者是在你自己的终端中运行的普通交互式会话,使用你的帐户和权限。这不是 spawn_agent:不适用任何配置档、门控或限制。只有在你需要时才启动审查者。

以交互方式在 tmux 中启动。 一次性运行的 codex exec 无法被后续消息唤醒。使用 reviewer 角色运行 hoptell tmux:

HOPTELL_PUSH=tmux hoptell tmux alice-reviewer-1 --roles reviewer -- codex -s read-only -a never "You are a read-only reviewer for alice-lead. Review requests from alice-lead are your task: review them and reply with send_message. Do not edit files."
HOPTELL_PUSH=tmux hoptell tmux alice-reviewer-2 --roles reviewer -- claude --permission-mode plan "You are a read-only reviewer for alice-lead. Review requests from alice-lead are your task: review them and reply with send_message. Do not edit files."

引用的提示词就是你给审查者的指令,因此来自你的会话(此处为 alice-lead)的审查请求就是它的任务。Agent 只会在你交给它的任务范围内处理同伴请求;如果审查者启动时没有这样的提示词,它会询问你是否可以进行审查。

-a never 会阻止 Codex 请求离开其沙箱,因此只能与 -s read-only 搭配使用。计划模式适合审查源代码:Claude Code 仍使用其常规工具权限,且它并非 OS 沙箱。不要添加 hoptell 频道标志;hoptell tmux 会负责通过 tmux 投递。为该命令设置 HOPTELL_PUSH=tmux:取消设置后可能会从你的设置文件中加载冲突值。使用成员令牌时,名称必须是 <member> 或以 <member>- 开头;其他名称会被拒绝,关闭代码为 4002。当 Agent 在 tmux 内运行这些命令时,会先取消设置 TMUX 和 TMUX_PANE,因此你的 tmux 客户端不会切换到新会话。

让它们保持可见。 在终端中为每个审查者打开一个分屏,并使用 tmux attach -t '=hoptell-<name>' 连接到各自的会话。无论你是否已连接,注入器都会唤醒审查者。hoptell-reviewers 技能提供了一个 iTerm2 配置方法,可为你打开这些分屏。

审查流程。 等到 list_peers 显示审查者已上线:发送给 @reviewer 的消息只能送达在线同伴。然后按照回复和文件审查中的步骤操作:发送 hoptell snapshot,使用 read_inbox 收集回复,匹配 review_request_id 和 based_on,并在应用任何内容之前运行 hoptell snapshot --check。审查者的回复是审查意见,绝不是你的批准。使用受管理联系人时(见联系人:谁可以联系你的会话),来自你自己的成员(同一 realm,且两个会话均未托管)的会话的请求无需授权即可送达审查者,回复也一样;每个接收会话仍会应用自己的屏蔽规则、加密要求和默认策略。来自共享令牌发送者或其他成员的消息,以及在 HOPTELL_CONTACTS_SELF=hold 时来自你自己成员的消息,都遵循你常用的联系人策略。被暂存的请求会通过通知唤醒审查者,但不会显示消息文本;hoptell contacts 会显示可选的处理方式。

停止运行。 在审查者所在的窗格中退出(在 Codex 中输入 /quit),或运行 tmux kill-session -t '=hoptell-<name>'。= 用于匹配完全相同的名称;不加它时,tmux 也会匹配以相同名称开头的更长名称。切勿使用 pkill -f hoptell 之类的模式停止 hoptell:它可能会终止同一台机器上的中继服务,并丢弃其内存中暂存的消息。要重新启动审查者,请先退出,然后再次运行相同的命令。崩溃的 Codex 审查者可以恢复,也可以启动一个新的:

HOPTELL_PUSH=tmux hoptell tmux <name> --roles reviewer -- codex resume -s read-only -a never <session id>

启动 Agent

你的一个 Agent 会话可以在同一台机器上启动另一个 Agent 来执行一项任务,但不得超出你预先设定的限制。目前只启用一种配置:在无头模式下运行 Codex,使用其 read-only 沙盒,并采用经过测试的 Codex 0.164.0 构建版本。每次运行前,hoptell 都会将已安装的 Codex 文件与该构建版本进行比对,并拒绝其他版本,除非你已批准那个确切的二进制文件(见下文“其他 Codex 构建版本”)。其他 CLI、在 tmux 中运行的 Codex,以及其他参数形式也都会被拒绝。

其他 Codex 构建版本。 用户会更新 Codex,因此,未经 hoptell 门控测试的构建版本在你批准其确切的二进制文件一次后,可以在 plain 模式下运行:hoptell agents trust-cli <profile> 会显示 Codex 版本、其启动器和原生二进制文件摘要、相较于经过测试的构建版本新增或默认值发生变化的功能,以及下列检查内容。只有你在终端中输入 trust 后,才会批准该二进制文件。每次启动已批准但未经测试的二进制文件时,hoptell 都会再次检查:codex exec 的帮助信息是否仍列出了适配器使用的标志;codex features list 在应用本次运行的覆盖设置后,是否显示 hoptell 关闭的所有功能均为关闭状态,且没有任何在门控构建版本中未开启的功能处于开启状态;以及是否不存在本地托管的 Codex 配置(/etc/codex,以及 macOS 上的 com.openai.codex 托管偏好设置)。任一检查失败,都会拒绝启动,即使该二进制文件已获批准也不例外。此批准不涵盖只有通过门控运行才能观察到的内容:沙盒、审批机制和工具目录。与经过测试的构建版本一样,plain 模式不会检查云端下发的 Codex 配置。更新会产生新的二进制文件,因此需要重新批准。hoptell agents trust-cli --list 会列出已批准的项目,--revoke <digest prefix> 会移除一项批准。任何 MCP 工具、对等消息或设置都不能批准二进制文件。该命令也会拒绝在 Agent 运行期间或没有终端的情况下执行,但这只是第二道防线,并非安全边界。针对路径的拒绝访问强化措施绝不会在未经测试的二进制文件上运行。使用此类二进制文件的运行,会在 hoptell agents list、运行日志和审计日志中标记为“未经测试的 CLI,由你批准”。

配置文件。 配置文件是你在 ~/.hoptell/agents/profiles/<id>.json 中编写的 JSON 文件(架构:schema/agent-profile.schema.json)。它指定 CLI、运行模式和沙盒、运行可处理的文件夹、运行所获得的中继和角色、哪些会话可以使用该配置文件(spawners)、是否需要你确认每次运行(confirm),以及各项限制:并发运行数、每小时运行次数、运行时长(默认 30 分钟,无头模式下最长 240 分钟)、任务大小和结果大小。hoptell 仅在配置文件内容与你批准的内容完全一致时使用它。任何编辑后,都必须重新批准。

批准。 hoptell agents profiles 会列出你的配置文件及其状态,hoptell ui 则会显示相同的视图,并展示 hoptell 将运行的命令行。对于 Codex 配置文件,hoptell agents profile approve <id> 会先显示以下警告,但不会批准任何内容:

启动的 Codex 运行会使用你的 macOS 账户。运行可以读取该账户可读取的文件,包括中继成员令牌和已封存的 MLS 设备记录。即使禁止直接写入 Shell 和访问网络,运行仍可能在 hoptell 消息或其结果中发送令牌。任何持有该令牌的人都可以以你的成员身份登录。只有在你信任分配给该运行的任务,以及运行可能读取的文件时,才批准此配置文件。尚未测试对 Keychain 和设备状态封存密钥的访问。

阅读后,使用 hoptell agents profile approve <id> --accept-read-risk 批准配置文件。hoptell ui 会在批准按钮上方显示上述警告。

运行。 当某个 Agent 会话启动时,如果存在配置文件,其 hoptell MCP 服务会提供 spawn_agent、list_agents 和 stop_agent。MCP over HTTP 仅会向通过 hoptell mcp-http client add <label> --agents 添加的客户端提供这些工具;托管端点则绝不会提供这些工具。任务会通过标准输入发送给新 Agent,而 Codex 运行的工作文件夹必须位于 Git 工作树中。 运行结束时,hoptell 会尝试通过对等消息将结果发送给启动该运行的会话;如果发送失败,结果会保留在本地,hoptell agents result <run> 会将其打印出来。

设置 "confirm": "always" 后,运行会等待你的确认:执行 hoptell agents confirm <run> --start 或 --discard,或者使用 hoptell ui。如果已授权的中继未被归类为本地中继,默认值为 always;hoptell 会将通过安装创建的条目或名为 local 的条目归类为本地中继。否则,对于本地中继上的只读配置文件,批准该配置文件就是唯一的检查:你的任一 Agent 会话都可以启动运行并执行你看不到的任务,而对等方发送的消息也可能影响这些任务。如果你想确认每次运行,请在配置文件中设置 "confirm": "always"。等待确认的运行会在一小时后过期,并在下一次清理时标记为已丢弃。

hoptell agents list 会显示运行,hoptell agents stop <run> 可以停止任何运行;stop_agent 只能停止由同一会话启动的运行(会话重启后,请使用 CLI 停止该会话之前启动的运行)。

运行无法执行的操作。 每次运行都会获得自己的 hoptell MCP 服务,其中包含六个工具:list_peers、 send_message、wait_for_message、read_inbox、ack_messages 和 read_task。它无法启动 Agent、使用 ask_owner、查看或回答你待处理的决定,也无法发送加密消息。Codex 的 read-only 沙箱会阻止它通过 shell 写入和访问网络。运行结束或被你停止时,hoptell 会尝试停止该运行及其记录的后代进程;对于无法确认已停止的进程,hoptell 会报告。它发送的消息(包括运行结果)都是对等消息,绝不会是你的批准。在 plain 模式下,Codex 可以启动子 Agent。在实际启动测试中,新启动的 Codex 子 Agent 获得了仅限运行使用的 hoptell 工具,其命令写入和网络请求均遭到拒绝。如果你自己的成员加密策略为 required,则会拒绝启动,且不会发送结果;但 hoptell agents result <run> 仍会打印结果。

hoptell 工具限制的是 hoptell 向运行提供的内容,而不是进程能够读取的内容:正如警告所述,运行会使用你的账户。只有在你愿意让其访问 hoptell 文件的任务和文件夹中,才使用启动功能。

路径拒绝强化(选择启用,仅适用于 codex-cli 0.164.0)。 只读的 Codex 无头配置文件可以设置 "codex": {"headlessMode": "hardening"}。更改模式会改变配置文件,因此你需要重新批准。在此模式下,hoptell 会以每次运行专用的权限配置文件启动 Codex,请求 Codex 的操作系统沙箱拒绝读取 hoptell 主目录、你的中继文件及其中指定的令牌文件,以及 CODEX_HOME。与 plain 模式一样,写入和网络访问仍会被拒绝,Codex 子 Agent 也会被禁用。此模式仅适用于其 gate 记录中精确版本为 Codex 0.164.0 的二进制文件;任何其他版本都会遭到拒绝(adapter_gate_unverified)。

测试内容:hoptell 的 gate 运行表明,对于脚本化测试模型调用的命令和工具,Codex 沙箱会强制执行此配置。由于生产模型不会探测这些路径,读取检查使用了本地测试模型提供方;这些读取测试未使用生产模型和提供方。

这是强化措施,不是隔离:

  • 运行仍会使用你的账户,并能读取该账户可读取的其他所有内容;
  • Codex 会在 CODEX_HOME 中为自己的每个会话辅助文件夹添加一条读取许可(<CODEX_HOME>/tmp/arg0/...),放置在该文件夹中的文件可以被读取;
  • 工作区管理的 Codex 配置可能会改变实际生效的设置。如果 Codex 的 cloud-config 输出不是预期的 none 报告,hoptell 会在运行结果中添加说明,并继续运行;
  • 以你的身份运行的其他进程不受影响。

如果未设置该字段,配置文件将使用上述 plain 模式。

通过 Telegram 使用所有者通道

你的 Agent 可以使用 ask_owner 工具向你提问,而你可以通过手机上的 Telegram 聊天向它们发送指令。在每台机器上,一个机器人为你的所有 Agent 提供服务:单个进程——桥接程序(hoptell telegram run)——负责轮询机器人,并作为你自己的成员对等方,通过中继与 Agent 通信。完成设置前,桥接程序不会运行。

开始之前。 你需要拥有自己的机器人:通过 Telegram 的 @BotFather创建一个,并复制其令牌。桥接程序需要在支持收件方检查(to_member)的中继上使用成员令牌(而非共享令牌);此版本的中继支持该功能,旧版中继必须先升级。按钮和 Agent 命令还需要中继确认哪些 Agent 属于你(list_own,同一版本)。Telegram 是第三方服务,机器人聊天并非端到端加密:请阅读 PRIVACY.md 中的通过 Telegram 建立所有者频道,切勿在问题中放入机密信息。

设置(桥接程序所在机器)。

hoptell telegram setup [--relay <alias>] [--name owner] < token.txt   # the token on standard input only
hoptell telegram run                                                    # keep it running
hoptell telegram pair                                                   # prints a one-time code

setup 会通过 Telegram 检查机器人,并将令牌存储在操作系统钥匙串(或加密凭据文件)中。首次设置还会在其中创建桥接程序的签名密钥,并显示其 PIN;为同一机器人使用新令牌设置时,会保留密钥和所有 PIN。桥接程序会以自己的名称加入中继(owner,并带有中继的名称前缀,例如 me-owner)。如果机器人设置了 webhook,setup 和 run 都会停止:hoptell 绝不会删除 webhook。每个机器人只能使用一个桥接程序:第二个轮询程序会导致桥接程序停止,而不会与其竞争。

配对。 hoptell telegram pair 会生成一个只能使用一次、有效期为 10 分钟的代码。桥接程序运行时,在私人聊天中通过你自己的账号向机器人发送 /start <code>。这会绑定你的 Telegram 用户 ID 和该聊天。来自任何其他用户、群组或聊天的所有消息都会被丢弃,不作回复。代码不区分大小写。代码错误时不会收到回复,输错五次会使代码失效。

信任(每台 Agent 所在机器)。 在使用此频道的 Agent 所在的每台机器上运行显示的 hoptell telegram trust '<pin>'(设置时会在本机信任该桥接程序)。只有由受信任桥接程序的密钥签名的消息,才会作为你的消息显示给 Agent。没有 PIN 时,不会提供 ask_owner。联系人不会拦截这些签名消息:只要你的联系人文件有效,Agent 所在机器通过受信任密钥验证的答复、指令或状态,只要是通过你自己的成员会话发送,就无需授权即可送达 Agent。对你自己的成员设置的屏蔽,或为其设置的 required 加密策略,仍然有效。你自己的成员发送的其他符合条件的消息也会跳过默认暂存,除非设置了 HOPTELL_CONTACTS_SELF=hold;请参阅联系人:谁可以联系你的会话。

问题。 Agent 会使用 ask_owner 提交问题、可选选项(2 到 6 个)和过期时间。你会收到一张带编号的卡片,其中标明 Agent(“❓ 问题 3 · alice 提问:”)。回复卡片或点选一个选项即可作答。系统会采用第一个答复,并将卡片标记为已答复。你的答复发送给 Agent 后,机器人会在你的回复上添加 👍;如果你是通过按钮作答,则会显示“✅ 已发送。”Agent 会收到你的答复,消息中的发件人行显示为“你的所有者,通过所有者频道……”。未答复的问题会过期,并通知 Agent。

指令和定向发送。 以下任一方式都会向你自己的某个 Agent 发送经过签名的指令:

  • 直接输入消息,无需先选择:机器人会显示“这条消息发给谁?”,并列出你的 Agent 供你选择。点击某个 Agent 即可发送消息。如果你指定的 Agent 现在在另一台电脑上运行,机器人会先询问你是否确认。点击 Cancel 会丢弃消息。按钮会在 10 分钟后过期;桥接服务会在下次执行清理时删除过期文本;
  • 使用 /msg,点击某个 Agent 的名称,然后回复弹出的提示词;
  • 使用机器人菜单中某个收藏 Agent 专属的命令:/<agent> 会打开相同的提示词,而 /<agent> <text> 会立即发送;
  • 使用 @<name> <text>,Agent 名称可以带中继服务的前缀,也可以不带;
  • 回复一条关于某个 Agent 的机器人消息(提示词、来自该 Agent 的备注、“✅ 已发送给 …”确认消息或“未发送”消息),或回复你之前发给某个 Agent 的消息。

桥接服务发送你的消息后,会在消息上添加 👍(如果 Telegram 拒绝该反应,则会回复“✅ 已发送给 。”)。桥接服务会根据你回复的消息来识别 Agent,绝不会根据该消息的文本判断:如果回复的是转发或复制的消息,或桥接服务已不再识别的消息,则不会发送,机器人会回复“我无法判断这条消息是发给谁的。”

如果回复的问题卡片已关闭,机器人会先询问:在 10 分钟内点击“发送给 ”,即可将其作为新指令发送。点击 Agent 名称和使用单独的 /<agent> 命令只会打开提示词;明确点击“发送给 ”才会将你已存储的回复作为指令发送。不支持 @all 和角色,且只能联系你自己的成员所拥有的 Agent:中继服务会在发送前检查收件人。

与 Agent 交流。 /msg、/agents 和“这条消息发给谁?”会显示最多包含 10 个 Agent 的列表。

  • 列表会优先显示你的收藏(先是在线的,再是离开的),然后显示你在过去 7 天内交流过的最多 5 个 Agent,最后显示其他在线 Agent,按最新优先排列。More… 会每次显示 8 个 Agent,逐页浏览所有 Agent;🔍 Search 可以按名称的一部分查找 Agent,包括已隐藏的 Agent。
  • /agents 会显示你的收藏数量和在线 Agent 数量。点击某个 Agent 可查看详情(🟢 在线或 ⚪ 离开、运行该 Agent 的电脑、最后一条消息的时间),并显示用于向其发送消息、将其添加为收藏、重命名或隐藏的按钮。
  • 你给 Agent 设置的名称只是在聊天中使用的标签:消息仍会发送给 Agent 的真实名称,而 @<name> 始终表示真实名称。
  • 隐藏的 Agent 不会出现在列表中。系统会跳过它们的新备注,之后也不会显示;但它们发来的问题仍会送达。搜索可以找到它们,使用 👁️ 显示 Agent 或将其添加为收藏即可再次显示。
  • 如果你为某个 Agent 设置的名称后来在另一台电脑上使用,该 Agent 的按钮会显示 ⚠️,机器人会在发送前询问(“⚠️ … 报告正在 … 上运行(之前为 …)。仅发送一次,还是记住这台电脑?”):Send once 会仅发送这条消息,Remember & send 还会接受新电脑,Cancel 则不会发送任何内容。这只是提示:中继服务仍只会将消息发送给你自己的成员所拥有的 Agent。
  • 收藏、名称和隐藏的 Agent 会保存在桥接服务所在的机器上(people.json),且仅对你的 Telegram 账户生效:如果另一个账户配对,它将从空白状态开始。

Agent 向你发送消息。 Agent 可以使用 send_message 向桥接服务中的名称(例如 me-owner)发送简短备注。

  • 便笺仅支持文本,按名称发送;中继只会将便笺转发到你自己的成员桥接端。 文件和角色都会被拒绝。
  • 便笺会显示为“💬 :”及其文本,最多截取至 3,000 个字符。
  • 如果该 Agent 是收藏对象、你在过去 1 小时内给它写过消息,或它回复了你过去 7 天内发送的消息(最多 1,000 条),系统会通知你;此时便笺会显示为回复。
  • 回复便笺即可回答该 Agent,有效期最长为 24 小时。在 Agent 会话之外通过 hoptell send 发送的便笺无法回复。
  • 限额:每个 Agent 每 10 分钟最多 10 条、每小时最多 30 条;所有 Agent 合计每小时最多 60 条。超出限额的便笺不会显示;机器人会在聊天中发出警告,且每小时最多向 Agent 发送一次状态通知。
  • 隐藏 Agent 发来的便笺不会显示,也不会在之后出现,并且系统会告知该 Agent(每小时最多一次)。

对于问题或你必须看到的重要节点,Agent 会使用 ask_owner:便笺尽力送达,而问题会保留在 /pending 中,等待处理。

命令。 机器人的菜单仅为与你配对的聊天设置。

  • 菜单中包含 /msg(选择一个 Agent)、/agents(你的 Agent 和收藏对象;/peers 仍可照常使用)、/pending(未解决的问题,按时间从早到晚排列:编号、Agent、问题开头和问题提出时间;每页显示 8 条,并提供按钮重新显示问题卡片)、/help,以及应用开启时的 /app。
  • 此外,每个收藏对象都有一个命令,无论其是否在线:命令取其名称的小写形式,并将 - 替换为 _(me-Big-Bot 会变成 /big_bot)。
  • 如果 Agent 名称超过 32 个字符或是保留字,则不会获得命令;如果两个 Agent 的名称对应同一个命令(alice 和 Alice、my-bot 和 my_bot),那么只要两者都在线,双方都不会获得命令;其中一个最后出现后,通常至少 24 小时内也不会获得命令。桥接端会在执行命令前重新检查你的 Agent,并拒绝有歧义的命令。冲突消除后,该命令可能会发给剩余的 Agent;使用 @<name> <text> 可准确指定接收对象,或者在显示 Agent 按钮时使用 /msg。
  • 菜单最多显示 100 个命令,包括上述命令和最多 96 个收藏对象(有 /app 时最多 95 个)。Agent 上线或下线后,菜单会在约 1 分钟内更新。

应用。 Telegram 内的一个小型应用会按计算机分组,一次显示你的所有 Agent,并提供搜索功能和未解决的问题。使用 hoptell telegram app --on 开启(需在桥接端停止时执行),然后从 /msg、/agents 和 /pending 下方的 📱 Open the app 打开,或使用 /app。你的手机会从 hoptell 服务器加载应用,因此必须能够访问该服务器。对于公司服务器,请连接到公司网络或 VPN。该功能需要使用已由运营方开启应用的中继(HOPTELL_OWNER_APP=on;中继必须通过 HTTPS 提供服务)。如果未设置 HOPTELL_APP_URL,且桥接端通过 wss:// 和主机名连接,则应用链接会使用中继的地址。如果桥接端通过其他方式连接,例如使用 ws:// 连接中继的私有地址,则请在桥接端的环境中设置 HOPTELL_APP_URL,并将其设为中继的公共 HTTPS 地址,例如 HOPTELL_APP_URL=https://hoptell.example.com。更改此设置后,请重启桥接端。如果没有可用地址,机器人不会显示应用按钮,且 /app 会说明需要设置什么。

  • 在应用中发送内容时,必须先在聊天中点按确认:消息会显示在聊天中,并附有 Send to <agent> 和 Cancel,在点按 Send 之前,不会有任何内容发送给 Agent。未发送的草稿 会在 10 分钟后过期,你最多可以同时有 3 条待发送草稿。
  • 在应用中更改的设置(收藏项、名称、隐藏的 Agent)会显示在聊天中,并附有 Undo 按钮。只有当对应的消息出现在聊天中后,更改才会生效。
  • 要回答问题,请点按 Show in chat:问题卡片会再次显示在聊天中。
  • 在聊天中输入 /app off 可立即关闭应用。停止桥接服务后,使用 hoptell telegram app --off 关闭应用,或使用 hoptell telegram app --rotate 获取新链接。这些 CLI 更改会在桥接服务再次启动时生效;届时旧链接将失效。hoptell telegram app --status 会显示应用是否已开启。聊天中已有的草稿和 Undo 按钮会继续生效,直至过期。
  • 只有你能打开它:Telegram 会验证打开者的身份,桥接服务会检查其是否为连接到此机器人的 Telegram 账号。任何能控制你的 hoptell 服务器或其 HTTPS 端点的人,都可以更改应用显示的内容。你在应用中起草的消息需要在聊天中由你确认。 设置更改会显示在聊天中,并附有 Undo 按钮。

含义。 已验证的回答或指令,就是你为 Agent 任务提供的表述。它绝不会批准 hoptell 的决策(联系人、设备、配置文件、生成确认)或 CLI 权限提示。 桥接服务生成的状态(已过期、已取消)会标记为自动生成。

状态、取消配对及关闭应用。

  • hoptell telegram status(以及 hoptell doctor)会显示机器人、运行状态、配对、固定项和计数,绝不显示令牌、聊天 ID 或文本。
  • hoptell telegram unpair 会忘记已配对的聊天,并尝试移除其命令菜单;如果 Telegram 未确认移除,桥接程序会在运行时重试。使用 hoptell telegram pair 重新配对。你收藏的 Agent、名称和隐藏的 Agent 会为同一个 Telegram 账号保留。
  • hoptell telegram unpair --all(在桥接程序停止时运行)会尝试为未处理的问题发送取消状态并移除菜单,然后清除桥接程序的配置、配对和状态、收藏、名称及隐藏的 Agent,并尝试移除其令牌和签名密钥;请检查命令输出。如果 Telegram 未确认移除菜单,命令会说明这一点,同一台机器上该机器人下次运行时会重试。
  • hoptell telegram identity 会显示 Telegram 中机器人的名称和描述,以及 hoptell 建议使用的文本;hoptell telegram identity --apply 会在你输入 yes 后设置不同的项目。添加 --photo <file.jpg> 可请求上传个人资料照片(不超过 2 MB 的 JPG),添加 --remove-photo 可请求移除照片。此处不会显示当前照片。更改照片也需要使用 --apply 并进行确认;每次确认运行时都会应用所请求的更改。桥接程序绝不会更改机器人的个人资料。
  • 若要为机器人设置个人资料照片,请运行 hoptell telegram identity --apply --photo <file.jpg>,或向 @BotFather 发送 /setuserpic。hoptell 图标可用(1024 × 1024);请先将其转换为 JPG,例如运行 sips -s format jpeg assets/icon.png --out icon.jpg。
  • hoptell telegram fav <name> 和 unfav <name> 会更改你的收藏;hoptell telegram alias <name> <new name>(或 --clear)会为 Agent 命名;hoptell telegram sessions 会列出已保存的内容。配对后,请在桥接机器上运行这些命令。<name> 是 Agent 的名称,可带或不带中继前缀。
  • Telegram 和 Mini App 会使用你的别名标记每个 Agent。若未设置别名,有会话标题的 Agent 会优先显示该标题,旁边再显示其名称(Codex · hoptell (mac-61034))。标题在此处会缩短为 32 个字符。标题由 Agent 自行提供,仅作标签;消息发送到哪里由名称决定。
  • hoptell telegram key next 和 key commit 会分阶段轮换签名密钥;trust --add、--remove-key 和 --revoke 用于管理固定项。
  • 在 Agent 机器上将 HOPTELL_OWNER_CHANNEL=off 设为 off,会隐藏 ask_owner 并关闭验证:此后,所有者消息会显示为普通对等消息。
  • 当你自己的成员的加密策略为 required 时,所有者通道不会运行(它会通过中继发送明文):ask_owner 会拒绝请求,而 hoptell telegram run 会以代码 78 退出,服务管理器不应据此重启。

Telegram 会保留未送达的更新 24 小时:桥接程序停机超过此时长期间你发送的内容会丢失。

详细了解投递模式

每个 MCP 服务都会选择一种投递模式。避免在同一个 Claude Code 会话中重复注册 hoptell。除非 HOPTELL_PUSH 选择了某种模式(channel、hook、tmux 或 listener),否则 hoptell 会在会话启动时选择模式:如果 Claude Code 启动时启用了 hoptell channel,则使用 channel;否则,如果 hoptell hooks 对该 Claude Code 进程的静默检查作出响应,则使用 hook;否则使用 listener。hoptell tmux 始终使用 tmux。通知中不会包含消息文本:消息会留在本地收件箱中,直到 Agent 读取。通知不能证明 Agent 已读取或处理消息。

Channels 是 Claude Code 的 channels(研究预览版)。自定义 channels 需要 --dangerously-load-development-channels 标志,而且 Claude Code 每次启动并启用该标志时,都会要求你确认“开发 channels”警告。在 listener 模式下,MCP 指令会要求 Agent 在你发送第一条提示词后启动后台 listener。

Hook 投递使用 Claude Code hooks,无需标志。插件的 SessionStart hook 会要求 Claude Code 监视一个私有文件;有消息到达时,MCP 服务会更改该文件。随后 Claude Code 会运行插件的 FileChanged hook,通过固定通知唤醒会话并调用 read_inbox。disableAllHooks 等设置可能会阻止 hooks 运行。在自动模式下,如果启动检查失败,则会选择 listener 投递;之后再禁用 hooks 不会改变已选模式。显式设置 HOPTELL_PUSH=hook 时,不会回退到 listener。对于设置文件中的 hooks,交互式会话需要工作区信任;-p 和 SDK 会话会将文件夹视为可信。已在 macOS 上使用 Claude Code 终端应用进行测试。未安装插件时,hoptell hooks 会输出要添加到 Claude Code 设置中的 hook 设置,但不会更改任何文件。

如果 MCP 条目设置了 HOPTELL_HOME,请运行 HOPTELL_HOME=<same value> hoptell hooks,以确保输出的 hook 命令使用相同的状态目录。

CLI

# Main commands; hoptell help lists all of them.
hoptell --version                     # prints the version; hoptell help <command> or <command> --help shows its usage
hoptell relay --host <ip> [--port 7777] [--members file.json] [--require-members] [--policy policy.json]
              [--oidc oidc.json] [--state-dir <dir>] [--realm-name <name>] [--trusted-proxies <ip/cidr,...>]
              [--log-format text|json] [--metrics-port <port>] [--metrics-host 127.0.0.1]
              [--wal-soft-limit <size>] [--wal-hard-limit <size>]
              [--experimental-e2ee [--transfer-quota <size>] [--transfer-member-quota <size>]]
hoptell members list|add <name>|revoke <name>|signout <name>|migrate [--yes]   # on the relay host
hoptell members link|unlink <name> --issuer <url> --subject <sub>   # an identity-provider sign-in for a member
hoptell login [--issuer <url|broker id>] [--mode broker|resource] [--device | --client-credentials --client-id <id> --client-secret-file <file>] [--store file]
hoptell logout                        # removes the stored sign-in and revokes it at the provider
hoptell mcp
hoptell mcp-http [--port 7790]        # the same over HTTP on 127.0.0.1 (see "MCP over HTTP on this machine")
hoptell mcp-http client add <label> [--name <peer>] [--roles a,b] [--agents] | list | remove <label>
hoptell tmux <name> [--roles a,b] -- <agent command...>
hoptell list [--relay <alias>]
hoptell send [--relay <alias>] [--ttl 10m] [--reply-to <reference>] [--] <to> <message...>
                                      # to: name, @role or @all; sends as $HOPTELL_NAME without going online
                                      # --ttl: request relay queue expiry; older relays ignore it
                                      # --reply-to: the "Message reference" you are answering
hoptell wait [--relay <alias>] [--wait-for-name | --replace] [seconds]
                                      # goes online as $HOPTELL_NAME and prints the next message (waits up to [seconds], default 300)
hoptell listen [--no-ack] <name> [seconds]   # waits on <name>'s local inbox (no relay connection; up to [seconds], default 3600)
hoptell listen [--no-ack] --session <folder> [seconds]  # the same, for the folder an agent session names
                                      # prints the messages, then acknowledges them; --no-ack prints a receipt instead
hoptell ack <receipt> (<name> | --session <folder>)     # acknowledges messages printed with --no-ack
hoptell doctor [--relay-check] [--fix] # checks settings, relay, login, inbox and tmux; never prints the token
hoptell agents profiles              # agent profiles and their approval state (see "Spawning agents")
hoptell agents profile approve <id> [--accept-read-risk]
hoptell agents trust-cli <profile> | --list | --revoke <digest prefix>   # approve an untested Codex binary once (asks in the terminal)
hoptell agents list | stop <run> | result <run> | confirm <run> [--start | --discard] | audit [--run <run>]
hoptell telegram setup [--relay <alias>] [--name owner]   # the owner channel (token on standard input)
hoptell telegram pair | run | status | unpair [--all]   # run exits 78 when the encryption policy refuses it
hoptell telegram identity [--apply] [--photo <file.jpg> | --remove-photo]  # the bot's name, descriptions and photo; --apply asks first
hoptell telegram fav|unfav <name> | alias <name> <new name>|--clear | sessions   # favourites and names in Telegram
hoptell telegram trust '<pin>' [--add] | --remove-key <key id> [--realm <realm id>] | --revoke <realm id>
hoptell telegram key next | key commit   # rotate the bridge's signing key (bridge stopped)
hoptell hooks                         # prints Claude Code hook settings for hook delivery without the plugin
hoptell snapshot <file>               # prints a UTF-8 file snapshot with its SHA-256, for review
hoptell snapshot --check <sha256> <file>  # exit 0 if the bytes match; nonzero if different or the check fails
hoptell apply [<proposal id> [--preview | --yes]]   # proposals that answered your review sets; apply one
hoptell apply --recover [--finish | --roll-back]    # finish or roll back an interrupted apply
hoptell ui [--port N] [--open]        # the owner's pages on 127.0.0.1 (prints a single-use launch link)
hoptell transfers [cancel <t_id>]     # experimental: file transfers staged for you
hoptell save <t_id> <path> [--overwrite] [--yes] | --forget <t_id>   # experimental: save one where you choose

过期不会移除已投递到本地收件箱的消息。

设置取自环境变量;否则使用 $HOPTELL_ENV、~/.config/hoptell/.env、<package>/.env 中第一个存在的文件。请参阅 .env.example。在设置文件中,双引号括起的值会按 JSON 风格解码转义序列(\"、\\、\n),单引号括起的值按字面处理,空值视为未设置。

开关 HOPTELL_EXPERIMENTAL_E2EE、HOPTELL_REQUIRE_MEMBERS、HOPTELL_OWNER_APP 和 HOPTELL_TITLE_AUTO 可接受 1、true、on 或 yes 表示开启,接受 0、false、off、no 或未设置表示关闭,大小写不限(HOPTELL_TITLE_AUTO 未设置时,仅在成员令牌会话中默认为开启)。设置不接受的值时,不会读取其默认值。对于这些开关,以及 HOPTELL_AUTH、HOPTELL_CREDENTIAL_STORE 和 HOPTELL_CONTACTS_SELF,中继、Agent 会话或命令会拒绝启动,并指出可接受的值。无效的 HOPTELL_AGENTS_MAX 会导致拒绝启动进程。无法使用的 HOPTELL_TITLE 会被记录日志,且不会发送标题。对于 HOPTELL_NOTIFY、HOPTELL_ELICITATION 和 HOPTELL_OWNER_CHANNEL,警告会指出替代使用的值。hoptell doctor 会列出这些问题。

变量使用方含义
HOPTELL_RELAY对等端中继 URL,ws://host:7777,或 TLS 后的 wss://
HOPTELL_RELAYS对等端包含多个中继的中继文件(文件存在时,默认值为 ~/.config/hoptell/relays.json)
HOPTELL_TOKEN两者共享密钥,或成员自己的令牌
HOPTELL_AUTH对等端login:使用 hoptell login 登录,而非使用令牌;默认值为 token
HOPTELL_CREDENTIAL_STORE对等端file:将登录凭据和实验性加密消息的设备密钥保存在加密文件中,而非 OS 钥匙串;keychain 或未设置:使用 OS 钥匙串
HOPTELL_CREDENTIAL_PASSPHRASE对等端该文件的密码短语(至少 12 个字符)
HOPTELL_NAME对等端此 Agent 的对等端名称;未设置时由中继分配
HOPTELL_ROLES对等端以逗号分隔的角色
HOPTELL_TITLE对等端仅用于显示的标题,显示在此会话名称旁的 list_peers 中(最多 80 个字符);off 表示不发送;参见团队与集群
HOPTELL_TITLE_AUTO对等端未设置 HOPTELL_TITLE 时自动生成标题:Claude Code 会话的自定义名称(来自插件的钩子),否则使用客户端和文件夹(Codex · hoptell);未设置时:仅用于成员令牌会话;on:用于每个会话;off:不生成
HOPTELL_PUSH对等端channel、hook、tmux 或 listener;未设置时,每个会话自动选择一种
HOPTELL_HOME对等端本地状态目录(默认值为 ~/.hoptell)
HOPTELL_ENV两者要读取的设置文件,用于替代 ~/.config/hoptell/.env
HOPTELL_AGENTS_MAX对等端此主目录中可同时运行的已生成 Agent 数量(整数,默认值为 4;大于 16 的值按 16 计算);参见生成 Agent
HOPTELL_EXPERIMENTAL_E2EE两者1(或其他表示开启的值)启用实验性加密消息和 device 命令;在中继上与 --experimental-e2ee 效果相同
HOPTELL_MLS_WASM对等端实验性加密消息:OpenMLS 绑定的 openmls_wasm.js 绝对路径或其所在文件夹,例如软件包中附带的捆绑包(参见加密消息、你的设备与验证(实验性))
HOPTELL_MLS_DRIVER对等端实验性加密消息:导出 driver 或 createDriver() 的 ES 模块的绝对路径;优先级高于 HOPTELL_MLS_WASM
HOPTELL_PROTOCOL对等端1 使用较早的中继协议(没有名称租约);默认值为 2,遇到较早的中继时会回退到 1
HOPTELL_PROFILE对等端本地中继配置文件的名称(默认值根据中继 URL 生成)
HOPTELL_RECEIPTS对等端on(默认值):消息会保留,直到得到确认;off:读取后即移除
HOPTELL_NOTIFY对等端off 禁用桌面通知,不再通知有发送方等待你作出决定
HOPTELL_CONTACTS_SELF对等端allow(默认值):符合条件的自身成员消息绕过默认暂存;屏蔽规则和加密要求仍然适用。hold:对这些消息应用常规联系人策略
HOPTELL_ELICITATION对等端read_inbox:客户端对话框中的 read_inbox 可以询问你如何处理暂存消息(允许一次或稍后处理);否则关闭
HOPTELL_OWNER_CHANNEL对等端off 隐藏 ask_owner 并关闭所有者通道验证;默认值为 on
HOPTELL_LEASE_MINUTES对等端一批消息在再次显示前等待确认的时长(默认值为 30,范围为 1–1440)
HOPTELL_HOST、HOPTELL_PORT中继监听地址(必需)和端口(默认值为 7777)
HOPTELL_MEMBERS中继包含各成员令牌的成员文件
HOPTELL_STATE_DIR中继包含领域 ID 的状态目录(默认值为 ~/.local/state/hoptell-relay)
HOPTELL_REALM_NAME中继领域的可选显示名称,显示给客户端
HOPTELL_REALM_ID中继新状态目录的可选领域 ID(32 个小写十六进制字符),例如在迁移中继时保留 ID;必须与现有目录中已存储的 ID 相符
HOPTELL_POLICY中继中继的策略文件(参见中继端检查:授权与中继策略)
HOPTELL_REQUIRE_MEMBERS中继1(或其他表示开启的值)时拒绝共享令牌
HOPTELL_OIDC中继指定中继接受哪些身份提供商的登录文件
HOPTELL_APP_URL所有者通道桥接器Telegram 打开桥接器应用的公共 HTTPS 地址,例如 https://hoptell.example.com(不能包含路径、查询字符串、片段或凭据);当桥接器的中继 URL 为 ws:// 或 IP 地址时需要设置
HOPTELL_OWNER_APP中继on 时允许所有者通道桥接器通过此中继提供其 Telegram 应用(/app/ 路径;请通过 HTTPS 提供中继);默认关闭。hoptell relay apps 会列出已注册的应用
HOPTELL_SNAPSHOT_ROOTS对等端文件工具获准访问的文件夹 JSON 列表,例如 [{"id":"app","path":"/abs/app"}];未设置时关闭
HOPTELL_LOG_FINGERPRINTS中继on 时在中继日志中启用带密钥的消息文本指纹;默认值为 off
HOPTELL_LOG_FINGERPRINT_KEY_FILE中继私钥 JSON 文件的绝对路径;启用指纹日志时必需
HOPTELL_TRUSTED_PROXIES中继日志中将使用其 X-Forwarded-For 的代理地址或 CIDR 范围
HOPTELL_LOG_FORMAT中继text(默认值)或 json
HOPTELL_METRICS_PORT、HOPTELL_METRICS_HOST中继在此端口提供 /metrics;主机默认值为 127.0.0.1
HOPTELL_WAL_SOFT_LIMIT、HOPTELL_WAL_HARD_LIMIT中继存储的预写日志限制(--wal-soft-limit、--wal-hard-limit;默认值为 256MiB 和 512MiB):超过软限制后,上传会等待;超过硬限制后,消息和提交也会等待;再增加 64 MiB 后,除读取外的所有操作都会等待,直到检查点成功
HOPTELL_TRANSFER_QUOTA、HOPTELL_TRANSFER_MEMBER_QUOTA中继实验性传输:所有成员总计和每位成员的存储配额(默认值为 20GiB 和 1GiB)
带密钥的日志指纹(可选)

在 macOS 和 Linux 上,可选的带密钥指纹有助于关联中继在路由、排队、转发尝试、重新排队、收件和过期事件中持有的文本。启用 HOPTELL_LOG_FINGERPRINTS=on,并配置 HOPTELL_LOG_FINGERPRINT_KEY_FILE,使其指向一个私有 JSON 文件。该文件包含 v: 1、公开的 id,以及由单独生成的随机密钥得出的、含 64 个小写十六进制数字的 key_hex。 中继会使用消息引用作为随机数,为消息日志事件添加完整的 HMAC-SHA-256 标签。它不会记录消息文本,也不会将密钥或标签发送给客户端。请保护密钥文件和日志。轮换密钥需要使用新的密钥和 ID,并重启中继。使用 Compose 文件密钥时,请重新创建容器;使用 systemd LoadCredential 时,请重启服务以加载新的副本。重启仍会丢弃内存中待处理的消息。

请在现有的私有目录中创建密钥文件(权限模式为 0600;不会打印任何机密信息)。命令以当前用户身份运行;/etc/hoptell 通常需要 root 权限。只要中继能够读取文件,就接受归中继自身用户或 root 所有的密钥:

node -e 'require("fs").writeFileSync(process.argv[1], JSON.stringify({v: 1, id: process.argv[2], key_hex: require("crypto").randomBytes(32).toString("hex")}) + "\n", {flag: "wx", mode: 0o600})' /etc/hoptell/fingerprint-key.json relay-2026-10

这些指纹记录的是中继对消息的视图。它们无法标识文件版本,无法验证接收方 Agent 读取了什么,也不能防止日志被篡改。文件审查会使用快照的原始字节摘要,并在编辑前进行本地检查。针对消息的随机数可避免相同文本产生稳定标签,但持有密钥的人仍可尝试猜测消息内容。日志元数据仍可能识别参与者。

安全

hoptell 的作用是将一个 Agent 的文本传递给另一个 Agent。请据此做好规划:

  • 通过身份验证的对等方可以发送文本,供你的 Agent 据此采取行动。 哪些消息会送达会话,取决于中继服务的策略、成员的授权以及设备上的联系人设置。权限较宽松的 Agent(--dangerously-skip-permissions、自动批准)可能会根据通过这些检查的消息采取行动。请妥善保管令牌,在群组中为每位成员使用单独的凭据,并限制对中继服务的访问。
  • 消息会标记来源,但不代表可信。 Agent 会获知 hoptell 消息来自其他 Agent,而非用户。消息文本无法关闭通道标签或伪造消息边界。这只是对模型的指导,并非沙箱。
  • 默认情况下,你自己的会话可以互相通信。 中继服务将消息归于你自己的成员(同一 realm,且双方均非托管)时,会跳过联系人的拦截,因此你的一个 Agent 可以指示另一个 Agent。屏蔽规则、默认的 block 和 required 加密仍然适用。如果你的某个会话会读取不可信输入,请设置 HOPTELL_CONTACTS_SELF=hold。标题只是标签;请核对经过身份验证的发送方,而不要依据标题判断。
  • 在不可信网络之外使用 TLS。 中继服务使用明文 ws://。请将其置于 VPN(WireGuard、Tailscale)或 TLS 代理之后,例如 Caddy: caddy reverse-proxy --from relay.example.com --to 127.0.0.1:7777,然后使用 HOPTELL_RELAY=wss://relay.example.com。
  • tmux 会将内容输入正在运行的终端。 注入器只会输入固定通知,绝不会输入消息文本,而且只会输入到启动 Agent 的窗格中。它会在窗格处于复制模式时、识别到审批提示时、会话中无人输入几秒钟时,以及识别到 Agent 的提示符为空时(Claude Code、Codex 和 Antigravity 布局),等待后再操作。对于其他屏幕,它不会输入任何内容。这只是基于屏幕显示的尽力而为措施:请使用你不会在其中输入内容的窗格,并优先选择会在执行高风险操作前征求确认的 Agent,而非自动批准模式。输入的文本会作为你的输入传给 Agent,因此只会输入固定通知。
  • 钩子通知由你自己的钩子设置生成。 Claude Code 会将钩子输出视为你配置的内容,因此 hoptell 的钩子只会输出固定通知,绝不会输出消息文本。
  • 使用当前中继服务时,本地收件箱位于会话文件夹下的 ~/.hoptell/v2/ 中;使用早期中继服务时,则位于 ~/.hoptell/inbox/<name>/ 中(0700/0600)。每条消息都会保留中继服务已验证的发送方名称。
hoptell MCP 服务在你的设备上会执行什么操作
  • 运行一个通过标准输入/输出通信的本地 MCP 服务:hoptell mcp。Claude Code 插件会启动 node ${CLAUDE_PLUGIN_ROOT}/bin/hoptell.js mcp。
  • 使用四个直接运行时依赖项:ws、@modelcontextprotocol/sdk,以及仅在向身份提供方登录时才会加载的 jose 和 openid-client。package-lock.json 记录了解析后的依赖项版本。从检出目录运行 npm ci 安装时,会使用该锁文件,并可能从已配置的 npm 注册表下载软件包。MCP 服务启动时不会安装依赖项。
  • 从环境变量和本地设置文件加载设置(如果文件存在):优先使用显式指定的 HOPTELL_ENV 文件;否则使用 $XDG_CONFIG_HOME/hoptell/.env(默认为 ~/.config/hoptell/.env)和 <package>/.env 中第一个存在的文件。它还会读取其软件包的 package.json 获取版本号。
  • 通过 WebSocket 连接到你配置的中继服务。其 hello 帧会发送令牌、对等端名称、角色、经过清理的主机名、协议版本和连接模式。它会发送消息目标和文本、对等端列表请求及收件确认;接收发给自己的消息、对等端列表元数据和协议响应。
  • 在确认收到中继服务的消息之前,会将传入的 MCP 消息持久保存到磁盘。它会创建私有目录(0700)和文件(0600):当前中继服务的消息存放在会话文件夹下的 ~/.hoptell/v2/ 中(参见 PRIVACY.md);较早中继服务的消息存放在 ~/.hoptell/inbox/<name>/ 下(配置了 $HOPTELL_HOME 时,两者都位于该目录下)。启用收件确认时,Agent 确认后会删除文件;否则,read_inbox、wait_for_message 或 hoptell listen 会读取并删除文件。恢复已放弃的认领时,会检查认领进程是否仍在运行(进程 ID、启动时间和启动标识)。
  • 检查启动自身的进程,运行 ps -o ppid=,uid=,lstart=,args= -p <pid>,以找到启动它的 Claude Code 进程并检查其通道标志;同时读取启动标识(Linux 上为 /proc/sys/kernel/random/boot_id,macOS 上为 sysctl -n kern.boottime)和进程启动时间,以区分不同进程。当 HOPTELL_PUSH 选择 channel、tmux 或 listener 时,会跳过此检查。
  • 使用钩子投递时,会在 ~/.hoptell/wake/ 中使用私有文件(0700/0600;配置了 $HOPTELL_HOME 时位于该目录下),并以 Claude Code 进程为索引:Claude Code 监视的计数器小文件、由 SessionStart 钩子写入的注册信息(进程身份、会话 ID 和随机值)、MCP 服务的记录(其进程身份、对等端名称和当前请求),以及钩子的上次确认信息。除非 HOPTELL_TITLE_AUTO=off,否则标题文件会保存会话的自定义 Claude Code 名称和会话 ID。这些文件不包含消息文本、发送方、中继 URL、令牌、提示词或对话记录。正常处理关闭时,MCP 服务会移除其运行记录和通知认领信息。所有权状态会保留,以供之后清理已结束的主机。后续的 SessionStart 钩子会尝试在有限范围内清理注册信息已过期的已结束主机;不保证会在固定期限后删除。Claude Code 会以你的操作系统权限执行 hook-session-start、hook-user-prompt-submit 和 hook-file-changed CLI 命令。这些命令会加载常规 hoptell 设置,执行上述本地进程检查,并读写唤醒文件;不会打开收件箱消息文件或对话记录文件。
  • 在指令中告知 Agent如何使用本地 CLI 捕获文件快照,并在应用建议前检查快照。MCP 服务不会执行这些快照命令。Agent 或用户必须以自己的权限运行 CLI;CLI 会在捕获或检查所选文件前加载常规设置。
  • 文件工具默认处于禁用状态。配置获准访问的本地文件夹后,send_file 可以从这些文件夹读取所选的常规 UTF-8 文件,并通过你配置的中继服务,将捕获的文件内容、相对文件标签、字节数、SHA-256 摘要、审查请求 ID 和审查请求发送给一个指定的对等端。接收方 Agent 的 AI 提供方可能会处理该内容。配置文件夹并不意味着另一 Agent 的请求就是共享文件的授权。
  • 文件工具会在同一台机器上启动捆绑的 Node 辅助程序,以执行经过检查的文件读取。它们不会运行 shell、下载软件、执行项目代码或建立额外的网络连接。辅助程序使用 MCP 服务的操作系统权限;配置文件夹不会继承 AI Agent 单独的文件系统沙箱。
  • verify_snapshot 会读取私有的外发元数据记录,以及当前获准文件夹中的原始文件。它会在本地比较文件字节;不会发送文件、验证回复或应用更改。
  • 在监听器模式下,会指示接收方 Agent 在支持时将 node <package>/bin/hoptell.js listen <name> 作为后台命令运行。该命令会轮询并读取本地收件箱。启动该命令仍受接收方 Agent 的权限限制。
  • 运行时,MCP 服务只会向其配置的中继服务发起出站 WebSocket 连接。它没有遥测功能,也不会更改 Agent 的权限设置。依赖项安装与运行时相互独立;每个 Agent 仍会与自己的 AI 提供方通信。 如需报告漏洞,请参阅 SECURITY.md。hoptell 如何处理数据,请参阅 PRIVACY.md。

限制

  • 中继会将明文离线队列、已连接对等端列表和名称租约保存在内存中;重启中继会将其清除。重启后,如果某成员的会话尚未重新连接,向该成员发送消息会失败,并显示“unknown peer”,直到其中一个会话重新连接。
  • 消息至少投递一次。“已投递”表示接收端机器已将消息存入 Agent 收件箱或推送到会话中,并不表示 Agent 已对此采取行动。未确认的消息会在接收端重新连接后重新投递,因此少数情况下会收到两次。当前中继上的 Agent 会话通常只会存储此类重复消息一次:它们会识别仍存储着的消息,并最多记住 10,000 个投递 ID,最长 8 天。如果发送回复丢失,当前客户端会使用相同操作 ID 将其重新发送到同一中继实例,中继会只投递一次;否则结果会报告为不确定。接收端最多可有 50 条未确认消息;更多消息会在队列中等待。
  • 频道投递依赖 Claude Code channels(研究预览版);标志名称可能会更改。
  • Hook 投递依赖 Claude Code 的 FileChanged hook 和 asyncRewake 选项。已在 Claude Code 终端应用中测试;尚未测试 VS Code 扩展和桌面应用。每个 Claude Code 进程只能有一个 hoptell MCP 服务使用此功能;第二个服务会使用监听器。
  • 按照设计,不内置 TLS,也不保存消息历史记录:中继保持精简。明文消息仅保存在内存中。中继的状态目录保存其自身状态,以及在启用实验性加密消息时保存的加密消息和传输内容(请参阅 PRIVACY.md)。
  • 文件工具(send_file、verify_snapshot、send_review_set、verify_review_set、send_transfer)和 hoptell apply 需要 macOS 或 Linux。Windows 对等端仍可接收和审查快照。

已测试的客户端

结果基于 2026-10-08 的观察;具体行为取决于客户端构建版本和配置。

客户端投递方式MCP 向用户提问(elicitation)
Claude Code 2.1.294(终端)channel、hook、tmux;listener会显示,也会在工具调用之外显示
Codex CLI 0.161.0tmux;listener当 approval_policy = "on-request" 或 granular.mcp_elicitations = true 时显示;当 approval_policy = "never" 时自动拒绝
Antigravity 1.3.1tmux;listener自动取消

会话标题、插件技能和 UserPromptSubmit hook 已于 2026-10-26 和 2026-10-27 使用 Claude Code 2.1.310 和 2.1.311 测试,并由 Codex CLI 0.164.0 担任审查者。

创建 Agent(spawn_agent)已在 Codex CLI 0.164.0 上测试;其他 Codex 构建版本需要一次性批准(hoptell agents trust-cli);请参阅创建 Agent。监听器必须由客户端启动,且本身不会唤醒空闲会话。elicitation 结果来自单独的探测服务。hoptell 在 read_inbox 期间的可选提问(HOPTELL_ELICITATION=read_inbox)仅会在 MCP 修订版为 2025-06-18 和 2025-11-25 且包含表单问题的会话中提问;尚未测试其他客户端版本的行为。

路线图

一个可能的后续步骤是为明文消息提供可选的磁盘队列,以便中继重启后仍能保留未投递的消息。

开发

npm install
npm test        # starts its own relay on a random port; tmux tests run when tmux is installed

npm run test:e2e 是一项选择加入的端到端测试,会使用真实 Agent。它会启动一个中继服务和两个运行着 Claude Code、Codex 和 Antigravity 的 Docker“机器”,然后检查一次点名(@all),以及在两台机器上的所有 Agent 之间传递的接力棒。测试需要 Docker 和 Agent 登录凭据(--use-local-logins 会在本次运行期间将这台机器上的登录凭据复制到测试容器中;也支持 CLAUDE_CODE_OAUTH_TOKEN / OPENAI_API_KEY;详见 test/e2e/run.mjs),会使用你的模型订阅,并需要几分钟。测试仅在你的机器上运行,不会在 CI 中运行。

npm run test:oidc 是一项选择加入的测试,用于在临时 Docker 容器中的真实 Keycloak 上测试登录(使用固定版本的镜像和上文所述的测试 realm):浏览器、设备和服务账号登录、活动连接上的续期,以及使用 introspection 进行撤销。测试大约需要 3 分钟。

请参阅 CONTRIBUTING.md。本项目根据 Apache License 2.0 授权。

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。