01给使用者
装好 agent,手机上就能看到
大多数情况下你什么都不用做。下面说明 BotBus 怎么找到 agent,以及找不到时去哪里看原因。
注册表里的 agent:装了就出现
电脑上装了 ACP 官方注册表里的 agent,BotBus 会自动认出来,不用配置。常见的这几家在手机和手表上显示自己的图标:
Gemini CLI
goose
OpenCode
Qwen Code
Kimi CLI
Cursor
GitHub Copilot
Cline
Mistral Vibe
Kilo
以及注册表里的其他 agent
- 发现到的 agent 默认启用。不想让它出现在手机上,在电脑的 BotBus 设置 →「本机 Agent」里关掉它;手表的设备页也能开关。
- BotBus 内置一份注册表快照,随 BotBus 更新。刚进注册表的 agent 要等 BotBus 的下一版。
- 其他注册表 agent 和自己写清单的 agent 显示默认的
>_图标。
公司或自己的 agent
不在注册表里的 agent,只要实现了 ACP,也能接进来。向它的开发者要一份清单文件(很多 agent 的安装程序会自动放好),放进这个文件夹:
~/.botbus/agents/<id>.json放进去就生效,不用重启 BotBus。删掉文件,agent 也就从手机上消失。
没看到你的 agent?
- 清单写错了BotBus 设置的「通用」页会多出一节「这些 ACP 清单没有生效」,逐个列出文件和原因(例如“文件名必须是 my-agent.json”)。旁边有「打开清单文件夹」和「重新检测」。
- 在终端里查运行
botbus agent list,会列出 BotBus 找到的 agent、来源、开关状态,以及没生效的清单和原因。botbus在/Applications/BotBus.app/Contents/Helpers/里。 - 第一次启动就失败注册表里的 agent 如果第一次连接就失败(起不来、不是 ACP、版本不对),BotBus 会把它藏起来,直到它重新安装或更新、或 BotBus 重启。
- 显示“请在电脑上登录”这个 agent 需要先登录。在电脑上按它自己的方式登录一次就好,手机上不能登录。
- 被停用了检查 BotBus 设置 →「本机 Agent」里它的开关。停用的 agent 在手机上显示为“没有启用”。
- 电脑上开的会话看不到实时进度在 agent 自己的终端或 IDE 里开的会话,只有 agent 实现了 BotBus 的反向扩展才会实时出现;否则最多在列表里看到标题和时间。
02给开发者
把你的 agent 接进 BotBus
BotBus 电脑端用 ACP v1 驱动本机的 agent。你只需要实现 ACP;配对、凭据和手机之间的连接都由 BotBus 处理。另有一个很小的「反向扩展」,让你自己界面里开的会话也实时出现在手机上。
三种接入方式
| 进 ACP 官方注册表 | 放清单文件 | 反向扩展 | |
|---|---|---|---|
| 你要做的 | 提交到 ACP 注册表 | 安装程序写 ~/.botbus/agents/<id>.json | 在前两种之一的基础上,进程主动连 ~/.botbus/run/acp.sock |
| 自动发现 | 是,按可执行文件名在本机找;随 BotBus 下一版生效 | 是,放好即生效,不用重启 | 常驻型的清单可以不写 command |
| 手机新建、续聊、审批、中断、看记录 | 是,BotBus 按需拉起子进程 | 是 | 按握手里声明的能力 |
| 你自己界面里开的会话 | 支持 session/list 时列出,只有标题、目录、时间 | 同左 | 实时:状态、消息、审批 |
- 内置的六个 agent 优先;注册表里对应它们的适配器(
claude-acp、codex-acp、pi-acp)会被跳过。同一个 id,清单优先于注册表。 - 注册表条目要本机真装了才算:在 PATH 和常见全局目录(Homebrew、npm / pnpm / bun / volta 的全局 bin、
~/.local/bin等)里找。npm 分发的,可执行文件的真实路径还必须落在node_modules/<包名>/下。 - 注册表 agent 的第一次握手就是验证,失败会被隐藏;
initialize超时和要求登录不算失败。清单 agent 失败照常显示并报错。
清单文件
放在 ~/.botbus/agents/<id>.json。BotBus 监视这个目录,增删改都即时生效。安装程序请直接把文件写进已有目录,不要先删整个目录再换一个进来。
{
"id": "my-agent",
"name": "My Agent",
"command": "/usr/local/bin/my-agent",
"args": ["--acp"],
"env": { "MY_AGENT_LOG": "warn" }
}| 字段 | 规则 |
|---|---|
id | 必填。[a-z0-9-],1–32 个字符,等于文件名。不能用保留 id:codex claude hermes pi openclaw dsh acp claude-acp codex-acp pi-acp |
name | 必填,非空,超过 40 字截断。手机和菜单里显示它 |
command | 可选。绝对路径(可以用 ~,必须可执行),或 PATH / 常见安装目录里能找到的名字。不写 = 只能经反向扩展接入 |
args | 可选,字符串数组 |
env | 可选,字符串到字符串,合并在 BotBus 自己的环境之上。可执行文件所在目录会补到 PATH 最前(node 脚本需要) |
子进程的工作目录是用户主目录,会话目录经 session/new 的 cwd 给出。清单暂不支持自定义图标。
BotBus 用到的 ACP
手机上的操作
| 手机上 | ACP | 说明 |
|---|---|---|
| 新建任务 | initialize → session/new → session/prompt | 按需拉起。session/new 一返回,手机就能看到这个任务 |
| 续聊 | session/prompt;会话不在进程里时先 session/load | 不支持 loadSession、或这一轮还在跑时失败 |
| 审批 | 回复 session/request_permission | 见下方「审批」 |
| 中断 | session/cancel | 挂起的审批同时回成 cancelled |
| 带图的消息 | image 内容块(base64) | 没声明 promptCapabilities.image 时直接失败,不悄悄只发文字 |
| 看对话记录 | 收集到的 session/update | 内存里没有时用 session/load 重放 |
| 看未提交的改动 | 不经过 ACP | BotBus 在 cwd 里只读 git |
子进程空闲 10 分钟后关掉;崩溃后不自动重启,下一条命令来时再拉起。
能力
initialize:BotBus 发protocolVersion: 1,你回的也必须正好是 1;10 秒没回算超时。BotBus 不提供fs和terminal,工具由 agent 自己执行,BotBus 只看和批。promptCapabilities.image:声明了才能从手机发图。agentCapabilities.loadSession:续聊不在当前进程里的会话、查看历史对话都要靠它。sessionCapabilities.list:让手机看到电脑上已有的会话(只有标题、目录、时间,只列 7 天内更新过的)。进程在跑时每 60 秒刷新,不在跑时每 10 分钟短暂拉起一次。
{"jsonrpc":"2.0","id":1,"method":"initialize","params":{
"protocolVersion":1,
"clientCapabilities":{"fs":{"readTextFile":false,"writeTextFile":false},"terminal":false},
"clientInfo":{"name":"botbus","version":"1.0"}}}审批
- 手机点允许时选
allow_once,没有才用allow_always;拒绝时先reject_once,其次reject_always。都没有就失败。 - 工具类型
execute显示为命令,edit/delete/move显示为改文件,其余显示为一般权限。 - 审批请求里的
toolCall往往只有toolCallId,请先发对应的tool_call通知,手机上才有标题可看。
把产物分享回手机
从手机发起的 session/new 与 session/load 会在 mcpServers 里带一个 botbus stdio server。照常启动它,agent 就能把截图、文件、链接和本机预览分享到手机上,不用做任何适配。
{
"name": "botbus",
"command": "/Applications/BotBus.app/Contents/Helpers/botbus",
"args": ["mcp"],
"env": [
{ "name": "BOTBUS_CLI", "value": "…" },
{ "name": "BOTBUS_TASK_TOKEN", "value": "…" },
{ "name": "BOTBUS_TOOLS_URL", "value": "http://127.0.0.1:…" }
]
}登录
initialize 返回了 authMethods、调用时又报 auth_required(-32000)的,手机上显示“请在电脑上登录 <name>”。BotBus 不从手机上登录。
状态
| 手机上 | 条件 |
|---|---|
| 运行中 | session/prompt 还没返回(反向连接:_botbus/turn started 之后) |
| 待审批 | 有挂起的 session/request_permission |
| 完成 | stopReason = end_turn |
| 已中断 | stopReason = cancelled;反向连接断开时正在跑的一轮 |
| 失败 | refusal、max_tokens、max_turn_requests、JSON-RPC 报错、进程崩溃 |
标题优先取 session_info_update,其次 session/list,最后取首条 prompt 前 80 字。思考过程不显示。
反向扩展 v1
用来让你自己的终端或 IDE 里开的会话实时出现在手机上。连接方向反过来,角色不变:你的进程仍是 ACP 的 agent,BotBus 仍是 client。
- socket:
~/.botbus/run/acp.sock。开始会话时检查它在不在:在就连,不在(BotBus 没运行)就什么都不做。 - JSON-RPC 2.0,每行一条(UTF-8,
\n结尾,和 ACP stdio 一样),单行上限 16 MiB。 - 只有同一用户的进程连得上:目录 0700、socket 0600,BotBus 还会核对对端 uid。不需要 task token。
握手
→ {"jsonrpc":"2.0","id":1,"method":"_botbus/hello",
"params":{"id":"my-agent","version":1,"pid":4321,
"capabilities":{"prompt":true,"cancel":true,"newSession":false}}}
← {"jsonrpc":"2.0","id":1,"result":{"accepted":true}}
← {"jsonrpc":"2.0","id":1,"result":{"accepted":false,"reason":"用户在 BotBus 里停用了这个 agent"}}id 必须是 BotBus 已发现(清单或注册表)且没被停用的 agent;version 目前只认 1;capabilities 缺省的项当 false。
agent → BotBus
| 消息 | 含义 |
|---|---|
_botbus/session | {sessionId, cwd, title?}。先宣告,之后这个会话的消息 BotBus 才认;cwd 必填 |
session/update | 和 ACP 一样 |
_botbus/turn | {sessionId, state, stopReason?}。没有 session/prompt 的返回可等,轮次 started / ended 要单独报 |
session/request_permission | 请求。同时在终端里照常问用户,谁先答用谁的 |
_botbus/permission_resolved | {sessionId, toolCallId}。终端先答了,BotBus 撤掉手机上的审批 |
{"jsonrpc":"2.0","method":"_botbus/session","params":{"sessionId":"s-42","cwd":"/Users/me/proj","title":"修登录页"}}
{"jsonrpc":"2.0","method":"_botbus/turn","params":{"sessionId":"s-42","state":"started"}}
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"s-42","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"好的,我先看看"}}}}
{"jsonrpc":"2.0","id":7,"method":"session/request_permission","params":{"sessionId":"s-42","toolCall":{"toolCallId":"call-1"},"options":[{"optionId":"a1","name":"允许","kind":"allow_once"},{"optionId":"r1","name":"拒绝","kind":"reject_once"}]}}
{"jsonrpc":"2.0","method":"_botbus/permission_resolved","params":{"sessionId":"s-42","toolCallId":"call-1"}}
{"jsonrpc":"2.0","method":"_botbus/turn","params":{"sessionId":"s-42","state":"ended","stopReason":"end_turn"}}BotBus → agent
| 声明的能力 | 消息 | 什么时候 |
|---|---|---|
prompt | session/prompt | 手机续聊。请让消息出现在你的界面里,像用户在电脑上敲的;照常回 stopReason。没声明时手机提示“请在电脑上继续” |
cancel | session/cancel | 手机点中断。没声明时提示“只能在电脑上中断” |
newSession | session/new + session/prompt | 手机新建任务,给常驻进程型 agent 用。没有 command 也没声明它的 agent 不出现在新建列表里 |
规则
- 收到
accepted: true再开始上报。握手前的通知会攒着(最多 100 条),只是尽力而为;握手前的请求一律回错误。10 秒内没握上手就断开。 - 不要回显 BotBus 发给你的 prompt。用户在你自己界面里敲的可以转成
user_message_chunk发来;BotBus 经session/prompt发的那句它已经记过了。 -32001表示 BotBus 没有答案(会话不归这条连接、被新审批顶掉、这一轮已结束、终端先答了):继续等终端里的回答,不要当成用户取消。真正的取消回{"outcome":{"outcome":"cancelled"}}。- 每条连接最多 50 个会话。另一条连接或 BotBus 自己的子进程正在驱动的会话不会被接管。
- 用户停用 agent、删掉清单或 BotBus 退出时,BotBus 直接断开。断开后停止上报,下次开始会话时再连;正在跑的一轮记成已中断。
自查
botbus 在 BotBus.app/Contents/Helpers/botbus。下面两个子命令不需要 BotBus 在运行。
$ botbus agent list
gemini Gemini CLI 注册表 已启用 /opt/homebrew/bin/gemini --acp
my-agent My Agent 清单 已启用 /usr/local/bin/my-agent --acp
my-daemon My Daemon 清单 已停用 (没有启动命令,只能经反向扩展接入)
✗ /Users/me/.botbus/agents/bad.json:文件名必须是 my-bad.json$ botbus agent check my-agent --prompt "hi"
ACP 协议版本: 1
loadSession: 是
图片: 否
session/list: 否
登录方式: 0 种
会话: 7f3c…
agent: 你好!
结束原因: end_turnlist只读清单、注册表快照和开关设置;列表里有不等于能用,注册表 agent 可能在第一次握手后被隐藏。check拉起 agent 跑initialize并打印能力;加--prompt时在临时目录里跑一轮。它不注入botbusMCP,审批一律回cancelled。没有command的 agent 不能check。退出码 0 成功、1 失败、2 用法错误。
已知限制
- 只支持 macOS 电脑端,agent 必须跑在装着 BotBus 的那台电脑上。
- ACP 没有“agent 在向用户提问”的信号,提问只会作为一条普通回复出现,不会显示“等待回复”。
- 经
session/list看到的会话只有静态信息;不实现它的 agent,只能看到 BotBus 拉起的和经反向扩展报的会话。 - 反向连接断开时,正在跑的一轮记成已中断,即使 agent 那边其实还在跑。反向连接上的消息暂不带图。
- 一台电脑最多同时接入 11 个 ACP agent,超出的按显示名排序后丢掉。
- stderr 只在内存里留最后一小段,用来显示出错原因,不写日志。