Core-only 把 botmux 当作受控的 HTTP 服务嵌入你自己的系统:单进程、固定端口、绑定 127.0.0.1,不需要任何飞书凭证 / bots.json / pm2 / dashboard。编排器(任务 runner、CI、后端服务、沙箱)通过 HTTP 驱动它跑 CLI(codex / claude-code 等)、轮询结果、按需拿到可写 Web 终端。一句话:core-only 是 headless、单租户、纯 loopback 的 botmux——保留完整 daemon IPC 契约,去掉飞书侧。
与普通 fleet 的区别
普通 botmux start(fleet) | botmux serve --api-only(core-only) | |
|---|---|---|
| 进程模型 | pm2 + dashboard + 每 bot 一个 daemon | 单进程前台运行,进程存活即服务存活 |
| 飞书凭证 | 需要 larkAppSecret,缺则启动失败 | 完全不需要,不构造飞书 Client |
| bot 配置来源 | ~/.botmux/bots.json / BOTS_CONFIG | 忽略两者,合成唯一 apiOnly bot(仍读全局 .env 与全局配置) |
| 出站通道 | 回复发飞书话题 | 无飞书出站(no-transport),结果只经 HTTP 取回 |
| 绑定 | 端口按需探测 | 固定端口、127.0.0.1、bind-or-fail |
| 状态目录 | ~/.botmux/data | 专用 ~/.botmux/core-only/<botId>/data(隔离) |
启动与参数
BOTMUX_API_PORT=8930 botmux serve --api-only # 推荐,前台运行
BOTMUX_CORE_ONLY=1 BOTMUX_API_PORT=8930 node <pkg>/dist/index-core-only.js # 等价,供嵌入式 spawn| 参数 | 环境变量 | 默认 | 说明 |
|---|---|---|---|
--port | BOTMUX_API_PORT | (必填) | 固定监听端口,bind-or-fail |
--bot | BOTMUX_API_ONLY_BOT | local_riff | 合成 bot id,须形如 local_<slug> |
--cli | BOTMUX_CORE_CLI | codex-app | 跑哪个 CLI |
--working-dir | BOTMUX_CORE_WORKING_DIR | 当前目录 | CLI 工作目录 |
--state-dir | BOTMUX_CORE_STATE_DIR | 专用状态根 | 状态目录 |
| (无 flag) | BOTMUX_CORE_MODEL | 未设 | 覆盖合成 bot 默认模型 |
--flag 优先于环境变量。--cli codex-app 走 app-server runner(结构化返回、无可视终端);--cli codex / claude-code 在 tmux pane 跑可视 TUI,可经 Web 终端围观/操作(见 web-terminal)。
就绪契约
daemon 先 bind 端口、后完成 durable restore,「端口能连」不等于「可以触发」:
- stdout ready line(锁定契约,正则
^\[core-only\] listening on):仅在 restore 完成后打印。 GET /healthz(公共免鉴权):未就绪503 {"ok":false,"status":"starting"},就绪200 {"ok":true}。- 就绪屏障同时作用于公共控制路由(restore 未完成时 trigger/trigger-result/insight 也返 503)。推荐 launcher 先轮询
/healthz到 200 再发第一条 trigger。 - 注意
/__health是 legacy liveness 探针、始终返 200,不能当就绪判断用。
三层鉴权
- 第一层 · integrator surface(core-only 专属,免 HMAC)——集成方只用这几条:
POST /api/trigger、GET /api/sessions/:id/trigger-result、GET /api/sessions/:id/insight、GET /healthz。这是刻意收窄的 allowlist(/api/asks/answer刻意不在内,防止同机 turn 劫持待答 ask)。 - 第二层 · 内部 capability / 签名路由:
session-ready、asks、slash/cd/close/chat-rename、hooks/emit、attention等,由会话内 rotating per-turn capability(绑定 URL 中 sessionId)或独立强签名在 handler 内验证;合法调用方是会话内 CLI 自身,集成方通常不直接调。 - 第三层 · host / operator 路由:其余全部(含可写终端
write-link、会话元信息GET /api/sessions/:id),需带 bind 的 loopback HMAC。
特例:POST /api/sessions/:id/close 既可用第三层 HMAC,也可用第二层会话内 capability(CLI 自关)。
HMAC 签名(最易踩的坑:bind)
三个 header:X-Botmux-Cli-Ts(Unix 秒,窗口 ±30s)、X-Botmux-Cli-Nonce(随机 hex,60s 防重放)、X-Botmux-Cli-Auth(HMAC_SHA256(secret, msg) 的 base64url)。密钥是 ~/.botmux/.dashboard-secret 里的原始 43 字符 base64url 字符串,直接当 key 用,不要再 base64 解码。
bind = `${METHOD} ${pathname} ${port}` // 例: "GET /api/sessions/<sid>/write-link 8930"
msg = `${ts}:${nonce}:${bind}`
三个易错点:port 是服务实际 bound 的端口(不是 Host 头的值);pathname 不含 query string;secret 用原始字符串。只签 bare ts:nonce(漏 bind)会得到 401 {reason:"sig_mismatch"},且往往第一次在 write-link 上才暴露。权威实现:src/core/daemon-ipc-auth.ts 的 daemonIpcAuthHeaders()。
触发与轮询
四态契约与普通 bot 一致(见 external-trigger),core-only 差异:
- 必须带 HTTP 应答模式:trigger 须带
options.waitForFinalOutput(同步)或options.asyncReturnSessionId(异步),否则400 bad_request(没有飞书群可回)。 - 异步返回合成
http_async_*chatId + 真实sessionId;用该 sessionId 轮询。 - 用户指令放顶层
instruction字段(渲染成 trusted<botmux_task>),不是prompt。 options.model/options.reasoningEffort对 codex 家族 fresh trigger 生效。- 轮询用完整 sessionId(UUID),不要用日志里的
[worker:xxxxxxxx]短 tag(会session_not_found)。 - completion 依赖从 CLI transcript 抽取 final_output;claude-code 首轮 ready-gate 回落导致永久 running 的 bug 已在 v3.9.0 修复,用 v3.9.0+。
可写 Web 终端
GET /api/sessions/:id/write-link(需 §HMAC 签名)返回带写 token 的 URL,打开即得可输入 xterm;只读版用viewToken。- backend 为
zmx时不支持(409 terminal_unsupported);tmux/pty 支持(见 tmux、zmx)。 - core-only 专属:只要有存活 worker 终端,公共
trigger-result响应会附带readOnlyUrl+viewToken(普通 fleet 不发射;closed/无 live worker 不发射)。写 token 永远只经 HMACwrite-link获取。 - token 本身稳定(host secret + sessionId 的 domain-separated HMAC,跨 restart 不变),但建议打开时现取 URL:代理端口/广告 host/worker 可用性会变。只持久化 sessionId。
- 单个 core-only 会话不会被空闲回收(无 idle 超时),worker 常驻、终端持续可达。
安全边界(no-transport)加固
- 不构造
Lark.Client,larkAppSecret对 worker withheld;入口删除process.env.BOTS_CONFIG,防止 worker 内 agent 读到真实 fleet 凭证。 - 冻结
SESSION_DATA_DIR到专用目录、BOTMUX_WORKER_HTTP_HOST与WEB_EXTERNAL_HOST冻结为127.0.0.1。 - 不跑 fleet 级 auto-restart /
botmux restart/ 共享 HOME breadcrumb,绝不触碰同机全局 botmux 安装。 - loopback 只是连通性不是身份(同机进程含 bwrap 沙箱都能拨 127.0.0.1),鉴权仍是硬门:除第一层三条控制路由 +
/healthz、/__health外全部要鉴权,没有任何一层「裸 loopback 就放行」。
端点速查
| 端点 | 方法 | 鉴权 | 用途 |
|---|---|---|---|
/healthz | GET | 公共 | 就绪探针(503 starting / 200 ok) |
/api/trigger | POST | 公共 | 发起一轮任务(须带 HTTP 应答模式) |
/api/sessions/:id/trigger-result | GET | 公共 | 轮询最终结果(四态);live worker 附 readOnlyUrl+viewToken |
/api/sessions/:id/insight | GET | 公共 | 轮询对话/进度 |
/api/sessions/:id/write-link | GET | HMAC + bind | 取可写终端 URL(建议打开时现取) |
/api/sessions/:id | GET | HMAC + bind | 会话元信息 |
/api/sessions/:id/close | POST | HMAC + bind 或会话内 capability | 取消/关闭会话 |
相关页面
- external-trigger — 触发/轮询/取消的四态契约
- web-terminal — Web 终端机制
- daemon-worker — daemon/worker 进程模型与 IPC