Otto Codex Companion 用来把 Codex 的生命周期事件同步给 Otto / Xiaozhi,让实体 Otto 可以像 Codex 桌面宠物一样提醒当前状态。
它的定位是:让 Otto 成为 Codex 的实体状态伴侣,播报 Codex 当前任务,并在 Xiaozhi / Otto 支持 Language Action Model 时通过简单动作表达运行状态。
当前版本的核心能力:
- Codex 开始工作、使用工具、等待权限、完成、失败、空闲时,桥接服务会更新状态。
- Xiaozhi 云端通过同一个 MCP 工具
get_codex_status查询状态。 - 返回 JSON 里包含
briefing.speak,Xiaozhi / Otto 可以播报“Codex 正在读取 README.md。”这类简报。 - 默认使用本地 mock transport,所以没有 Otto 硬件也可以先验证完整软件链路。
Codex lifecycle hook
-> hooks/codex-otto-hook.js
-> 本地 spool 文件:.otto-codex-bridge/hooks.jsonl
-> 桥接服务:npm run bridge
-> 状态机
-> 状态输出
- 本地 /status
- Xiaozhi MCP tool: get_codex_status
- mock transport 日志
hook 会先把事件写入本地 spool 文件,再由桥接服务消费。这样即使 Codex hook 进程所在环境不能访问 127.0.0.1,状态仍然可以通过文件可靠传递。
启用 xiaozhiMcpEndpoint 后,Xiaozhi 侧只会看到一个工具:
get_codex_status
这个工具返回当前桥接状态,包括:
stateMachine.currentState:当前状态,例如running、tool_running、idlemetrics.hooksReceived:收到的 hook 数量metrics.commandsSent:发出的状态命令数量metrics.lastRobotCommand:最近一次状态命令metrics.recentEvents:最近 hook 事件metrics.recentCommands:最近状态命令metrics.lastError:最近错误briefing:给 Otto 播报/展示用的简报
briefing 结构如下:
{
"title": "Codex 正在工作",
"body": "Codex 正在读取 README.md。",
"speak": "Codex 正在读取 README.md。",
"state": "tool_running",
"severity": "normal",
"needsUser": false,
"activity": "reading",
"target": "README.md",
"motion": {
"id": "tool_step",
"label": "工具小步",
"description": "短促踏步或抬脚,表示 Codex 正在使用工具。",
"lamInstruction": "请原地踏步两下,表示正在使用工具。",
"repeat": 2,
"intensity": "medium"
}
}Otto / Xiaozhi 最适合播报:
briefing.speak
如果设备支持 Language Action Model,也可以读取:
briefing.actionInstruction
briefing.motion.lamInstruction
例如 tool_running 会返回:
请原地踏步两下,表示正在使用工具。
当前动作映射保持简单:
| 状态 | 动作 ID | 含义 |
|---|---|---|
awake |
wake_nod |
点头唤醒 |
running |
thinking_sway |
思考轻摆 |
tool_running |
tool_step |
工具小步 |
waiting_approval |
approval_raise_hand |
举手提醒 |
done |
done_celebrate |
完成庆祝 |
failed |
failed_sad |
低头提示 |
idle |
idle_rest |
待机休息 |
| 机器人命令 | 桥接状态 | 触发事件 |
|---|---|---|
WAKE |
awake |
SessionStart |
RUNNING |
running |
UserPromptSubmit, SubagentStart |
TOOL |
tool_running |
PreToolUse, PreCompact, PostCompact |
ASK_APPROVAL |
waiting_approval |
PermissionRequest |
DONE |
done |
Stop, SubagentStop |
FAILED |
failed |
失败的 PostToolUse |
IDLE |
idle |
超过 idleAfterMs 没有新活动 |
默认时间参数:
cooldownMs:1800idleAfterMs:12000- hook spool 轮询间隔:
500ms
- Node.js 20 或更新版本。
- Codex CLI / Desktop 使用
/Users/LiangSirui/.codex/config.toml。 - 如需接入 Xiaozhi,需要在环境变量
XIAOZHI_MCP_ENDPOINT中放入有效的 Xiaozhi MCP 接入点。 - mock / Xiaozhi 状态查询路径不要求 Otto 硬件在线。
不要把 Xiaozhi token 写进 Git。token 只放在环境变量里。
先检查代码:
cd /Users/LiangSirui/Documents/robot
npm run check
npm test启动桥接服务:
cd /Users/LiangSirui/Documents/robot
export XIAOZHI_MCP_ENDPOINT="wss://api.xiaozhi.me/mcp/?token=你的真实 token"
npm run bridge这个终端需要一直开着。正常日志类似:
{"level":"info","message":"bridge started","host":"127.0.0.1","port":37378,"transport":"mock"}
{"level":"info","message":"xiaozhi mcp endpoint connected"}另开一个终端查看当前状态:
cd /Users/LiangSirui/Documents/robot
npm run status如果正常,输出 JSON 里会包含:
{
"briefing": {
"speak": "Codex 现在空闲。"
}
}当 Codex 正在使用工具时,briefing.speak 会变成类似:
Codex 正在读取 README.md。
Codex 正在搜索 xiaozhiMcpEndpoint。
Codex 正在运行测试。
Codex 正在检查代码。
Codex 正在等待你批准权限。
安装或更新全局 Codex hooks:
cd /Users/LiangSirui/Documents/robot
npm run codex:install-hooks安装脚本会:
- 修改
/Users/LiangSirui/.codex/config.toml; - 写入前创建带时间戳的备份;
- 只替换
# >>> otto-codex-bridge hooks >>>和# <<< otto-codex-bridge hooks <<<之间的块; - 使用新版 Codex hook-discovery 配置格式;
- 避免旧版错误的
[[hooks]]数组语法。
安装后的 hook 命令是:
node /Users/LiangSirui/Documents/robot/hooks/codex-otto-hook.js
hook 会把事件写到:
.otto-codex-bridge/hooks.jsonl
该目录已被 Git 忽略。
从 config/bridge.example.json 创建本地配置:
cd /Users/LiangSirui/Documents/robot
cp config/bridge.example.json config/bridge.local.json在 config/bridge.local.json 中启用:
{
"xiaozhiMcpEndpoint": {
"enabled": true,
"endpointEnv": "XIAOZHI_MCP_ENDPOINT",
"reconnectMinMs": 2000,
"reconnectMaxMs": 60000
}
}然后在同一个 shell 里设置接入点并启动:
cd /Users/LiangSirui/Documents/robot
export XIAOZHI_MCP_ENDPOINT="wss://api.xiaozhi.me/mcp/?token=你的真实 token"
npm run bridge在 Xiaozhi 里可以问:
Codex 现在在做什么?
查看 Codex 当前状态。
Xiaozhi 应调用 get_codex_status,并优先根据 briefing.speak 播报。
保持 npm run bridge 运行,再从另一个终端触发一次 Codex:
cd /Users/LiangSirui/Documents/robot
codex exec --dangerously-bypass-hook-trust --ephemeral -C /Users/LiangSirui/Documents/robot "请只输出 OK,不运行任何工具。"然后查看状态:
npm run status健康输出应满足:
metrics.hooksReceived大于0;stateMachine.currentState会从idle变成活跃状态;metrics.recentEvents有最近事件;briefing.speak有可播报文本。
如果 Xiaozhi 能查询但一直说空闲,先看 metrics.hooksReceived。如果它是 0,说明 Codex hook 还没有把事件送到桥接服务。
| 命令 | 作用 |
|---|---|
npm run bridge |
启动本地桥接服务,并可选连接 Xiaozhi MCP endpoint。 |
npm run status |
打印 127.0.0.1:37378/status 当前状态。 |
npm run otto:test -- COMMAND |
手动发送 RUNNING、DONE、FAILED 等状态命令。 |
npm run xiaozhi:serve |
启动独立 Xiaozhi MCP 连接 smoke test。 |
npm run codex:install-hooks |
安装全局 Codex hook 块。 |
npm run codex:hook:test |
用 fixture 测试 hook 脚本。 |
npm run check |
运行 Node 语法检查。 |
npm test |
运行测试套件。 |
mock transport 的机器人命令日志:
logs/mock-commands.jsonl
桥接服务日志:
logs/bridge.jsonl
hook spool 文件:
.otto-codex-bridge/hooks.jsonl
这些运行时文件都已被 Git 忽略。
先查看:
npm run status如果 metrics.hooksReceived 是 0,检查:
npm run bridge是否正在运行;- 修改代码后是否重启过
npm run bridge; - 是否运行过
npm run codex:install-hooks; /Users/LiangSirui/.codex/config.toml里是新版[hooks]配置,不是[[hooks]];.otto-codex-bridge/hooks.jsonl是否有写入权限。
运行:
codex doctor --allhooks 块不能使用 [[hooks]]。安装脚本会写入当前 Codex 可识别的 hook-discovery 格式:
[hooks]
SessionStart = [
{ hooks = [{ type = "command", command = "node /Users/LiangSirui/Documents/robot/hooks/codex-otto-hook.js" }] }
]确认:
XIAOZHI_MCP_ENDPOINT设置在运行npm run bridge的同一个 shell 中;- endpoint 以
wss://api.xiaozhi.me/mcp/?token=开头; config/bridge.local.json里启用了xiaozhiMcpEndpoint.enabled;- Xiaozhi 控制台能看到对应 MCP endpoint / device 在线。
部分 Codex 沙箱环境会阻止本地端口监听和本地 HTTP 访问。测试套件需要允许绑定 127.0.0.1 才能完整通过。生产 hook 路径不依赖 hook 进程访问本地 HTTP,因为它使用 spool 文件传递状态。
- 桥接服务只监听 loopback 地址。
- Xiaozhi endpoint token 必须只放在环境变量中。
config/bridge.local.json、.env、logs/、.otto-codex-bridge/都被 Git 忽略。- hook 脚本失败时会正常退出,不会阻塞 Codex 启动或对话。
- 本项目不会修改网络设置。