Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Otto Codex Companion

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,状态仍然可以通过文件可靠传递。

Xiaozhi 能读到什么

启用 xiaozhiMcpEndpoint 后,Xiaozhi 侧只会看到一个工具:

get_codex_status

这个工具返回当前桥接状态,包括:

  • stateMachine.currentState:当前状态,例如 runningtool_runningidle
  • metrics.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: 1800
  • idleAfterMs: 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

安装或更新全局 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 忽略。

启用 Xiaozhi MCP

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 手动发送 RUNNINGDONEFAILED 等状态命令。
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 忽略。

故障排查

Xiaozhi 能查询,但状态一直是 idle

先查看:

npm run status

如果 metrics.hooksReceived0,检查:

  • npm run bridge 是否正在运行;
  • 修改代码后是否重启过 npm run bridge
  • 是否运行过 npm run codex:install-hooks
  • /Users/LiangSirui/.codex/config.toml 里是新版 [hooks] 配置,不是 [[hooks]]
  • .otto-codex-bridge/hooks.jsonl 是否有写入权限。

Codex 无法启动

运行:

codex doctor --all

hooks 块不能使用 [[hooks]]。安装脚本会写入当前 Codex 可识别的 hook-discovery 格式:

[hooks]
SessionStart = [
  { hooks = [{ type = "command", command = "node /Users/LiangSirui/Documents/robot/hooks/codex-otto-hook.js" }] }
]

Xiaozhi MCP 连接不上

确认:

  • XIAOZHI_MCP_ENDPOINT 设置在运行 npm run bridge 的同一个 shell 中;
  • endpoint 以 wss://api.xiaozhi.me/mcp/?token= 开头;
  • config/bridge.local.json 里启用了 xiaozhiMcpEndpoint.enabled
  • Xiaozhi 控制台能看到对应 MCP endpoint / device 在线。

测试里无法监听 127.0.0.1

部分 Codex 沙箱环境会阻止本地端口监听和本地 HTTP 访问。测试套件需要允许绑定 127.0.0.1 才能完整通过。生产 hook 路径不依赖 hook 进程访问本地 HTTP,因为它使用 spool 文件传递状态。

安全说明

  • 桥接服务只监听 loopback 地址。
  • Xiaozhi endpoint token 必须只放在环境变量中。
  • config/bridge.local.json.envlogs/.otto-codex-bridge/ 都被 Git 忽略。
  • hook 脚本失败时会正常退出,不会阻塞 Codex 启动或对话。
  • 本项目不会修改网络设置。

更多文档

About

把 Codex 的生命周期事件同步给 Otto,让实体 Otto 可以像 Codex 桌面宠物一样提醒当前状态。

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages