最近做了一个很小的 Codex 插件,叫 Codex Lark Remote。
它解决的是一个我自己反复遇到的问题:我在 Mac 上打开 Codex,让它做代码分析、迁移、修 bug 或跑测试。很多任务并不需要我一直盯着屏幕,但中途又经常需要补充指令、确认方向、看一下进度。
如果人已经离开电脑,只能等回到 Mac 前再继续。这件事有点浪费。
所以我做了一个插件:在当前 Codex 对话里启动后,把这个对话交给飞书/Lark 机器人接管。之后你可以在飞书里继续给同一个 Codex 线程发消息,Codex 的关键进度和最终回答也会回到飞书里。
项目地址:
Codex Marketplace:
www.codex-marketplace.com/plugins/cod…
它解决的是什么问题
我想要的不是“远程服务器上跑一个 agent”,也不是“把仓库同步到云端再执行”。
我想要的是:
- 代码仍然在本机
- Codex 仍然在本机执行
- 当前 Codex 对话的上下文仍然保留
- 我只是临时离开 Mac,用飞书继续和它对话
典型场景是:
- 在 Codex Desktop 里打开一个项目。
- 让 Codex 开始分析或修改代码。
- 任务跑起来之后离开电脑。
- 在飞书里补充一句:“先别改 UI,优先把测试跑通。”
- Codex 收到这条消息,继续同一个本地对话。
- 飞书里收到关键进度、权限提示和最终结果。
这个模式的关键点是:飞书只是远程输入输出通道,不接管代码执行环境。
使用体验
第一次配置飞书/Lark 应用后,正常使用流程是:
- 在想继续的 Codex 对话里说:
启动 codex-lark-remote
- Codex 会要求你明确同意接管。
因为这会在本地 bridge 里保存当前 Codex thread 的路由状态。已有聊天历史不会发送到飞书/Lark,但接管期间之后的飞书消息和 Codex 回复会通过机器人传递。
-
同意后,插件启动本地 bridge,并绑定当前 Codex 线程。
-
之后直接在飞书/Lark 机器人里发普通消息即可。
比如:
检查 Alembic 文件夹的当前主要功能
或:
先不要改代码,只做架构分析
对 Codex 来说,这些消息会被当成同一个对话里的下一条用户消息。
为什么强调“当前窗口绑定”
这个插件最容易出错的地方,是同一个工作目录下可能同时开着多个 Codex 对话。
如果只按 cwd 绑定,就可能出现 A 窗口启动了飞书接管,B 窗口的消息却串流到飞书里。
所以现在的实现会严格绑定当前 Codex 窗口:
- 优先使用 Codex 工具调用里提供的精确 thread id
- 或使用当前 session path
- 如果拿不到按窗口区分的元数据,就直接阻止 handoff
- 不再按工作目录猜测“最近的会话”
这让启动流程更保守,但能避免远程接管时最危险的串流问题。
技术结构
插件里主要有几层:
1. Codex Plugin
插件通过 .codex-plugin/plugin.json 暴露给 Codex,包含:
- Skill:告诉 Codex 什么情况下应该使用这个插件
- MCP server:提供配置、诊断、启动接管、状态查询等工具
- 图标和插件元信息
2. MCP 工具
Codex 通过 MCP 工具执行本地操作,例如:
- 配置飞书 appId/appSecret
- 检查飞书鉴权
- 启动本地 bridge
- 将当前 Codex thread 交给 bridge
- 查询状态
- 停止接管
正常启动时,agent 不应该绕过 MCP 去跑本地脚本。这一点在 skill 里写得比较明确。
3. 本地 bridge
bridge 是一个本地 Node 进程,负责:
- 连接飞书/Lark WebSocket
- 接收机器人消息
- 根据 handoff 状态找到当前 Codex thread
- 调用
codex exec resume继续同一个线程 - 把 Codex 的关键进度和最终回答发回飞书
它不是云服务,只在本机运行。
4. 飞书/Lark WebSocket
插件优先使用飞书/Lark 长连接,不要求用户配置公网 URL 或 webhook callback。
配置时需要:
- 创建企业自建应用/内部应用
- 启用机器人能力
- 拿到 App ID 和 App Secret
- 在事件订阅里选择长连接/WebSocket
- 订阅
im.message.receive_v1 - 开通消息接收和回复权限
飞书里的输出怎么控制
一开始我把 Codex 的命令和输出都发到飞书,结果很快发现会刷屏。尤其是 cat、nl、sed、rg 这类源码查看命令,内容对手机阅读意义不大。
现在默认策略是:
- 最终回答正常发送
- 关键进度发送
- 普通 shell 命令默认不展示
Output:默认不展示- 风险命令仍然显示,并带
Warning: - 如果用户主动发送
/codex commands on,才展示命令 - 即使命令展示打开,Output 也只保留一行摘要
- 长消息会自动拆成多条飞书消息,而不是截断
这样飞书里看到的是“Codex 做到哪了、结论是什么、需要我做什么”,而不是整屏终端日志。
执行中的补充消息
还有一个场景:Codex 正在执行时,我又从飞书发了一条新消息。
当前实现不会尝试把文本热注入已经运行中的 Codex 进程。这样做不稳定,也容易让状态变乱。
它会把这条消息保存为“补充引导”,在飞书里回复已收到,并在当前轮结束后作为下一轮继续执行。
这更像正常的对话节奏,也更可控。
权限边界
这个插件有一个非常重要的限制:飞书不能点击 Codex Desktop 的原生权限弹窗。
比如:
- MCP 工具审批
- 沙箱提权
- 联网权限
- 安装依赖
- 写出工作区外的文件
- Codex Desktop 里的安全确认弹窗
这些不能被飞书自动处理。
所以插件做的是:要求 agent 遇到这类边界时,不要沉默等待,而是发回一条清晰的飞书提示,说明需要什么权限,以及用户是必须回到 Mac 上点确认,还是可以在飞书里用文字明确授权。
换句话说,飞书接管的是“对话流”,不是“桌面 UI 控制权”。
Mac 状态要求
因为 Codex 仍然在本机运行,所以 Mac 必须在线,不能睡眠。
插件在 macOS 上默认会在 handoff 期间启动:
caffeinate -dimsu
这表示屏幕可以熄灭,但系统不要睡眠。关闭 handoff 或 bridge 停止时,这个保活进程会一起停止。
如果你想自己管理睡眠,也可以在配置里关闭:
{
"handoff": {
"keepAwake": false
}
}
安装方式
最简单的安装方式:
npx codex-marketplace add GxFn/codex-lark-remote/plugins/codex-lark-remote --plugin --global
如果想固定到当前审核版本:
npx codex-marketplace add https://github.com/GxFn/codex-lark-remote/tree/v0.1.23/plugins/codex-lark-remote --plugin --global
插件页面:
www.codex-marketplace.com/plugins/cod…
GitHub:
配置飞书/Lark
创建飞书/Lark 应用后,把 appId、appSecret 和允许使用者配置给插件:
请配置 codex-lark-remote。
飞书应用:
- appId: cli_xxx
- appSecret: xxx
允许使用者:
- allowedUsers: ["ou_xxx"]
请用这些值调用 codex_lark_configure,然后运行 codex_lark_check_auth。
如果不知道自己的 sender id,可以先允许空列表启动,然后在飞书里发送:
/codex whoami
再把返回的 senderId 加入 allowedUsers。
私密配置默认写在:
~/.codex-lark-remote/config.json
不要把这个文件提交到仓库。
常用命令
/codex whoami
/codex status
/codex observe
/codex observe <number|thread-prefix>
/codex observe off
/codex commands on
/codex commands off
/codex handoff off
另外,“断开连接”“停止接管”“关闭观察”“打开命令显示”这类口语也会被识别。
适合谁
我觉得它适合这些场景:
- 你经常在本地用 Codex 跑长任务
- 你希望代码仍然留在自己的 Mac 上
- 你不想搭一套远程开发服务器
- 你已经在团队里使用飞书/Lark
- 你希望从手机上补充指令、看进度、接收最终结果
不适合的场景也很明确:
- 希望 Mac 关机后继续执行
- 希望自动处理所有权限弹窗
- 希望多人同时控制同一个 Codex 线程
- 希望把它当成完整的云端 coding agent
后续可能做什么
目前它还是一个很小的插件,后续我比较想继续打磨:
- 更自然的中文口语指令
- 更好的权限提示格式
- 更清晰的进度摘要
- 更完善的观察模式
- 更稳定的首次安装和诊断体验
如果你也在用 Codex 和飞书/Lark,欢迎试一下,也欢迎提 issue。