feishu_codex_bridge 是一个基于 lark-cli 的本地常驻守护工具,用来把飞书 P2P 私聊消息桥接到同一台机器上的长驻 Codex CLI 会话。
它的重点不是再造一层飞书协议封装,而是直接复用 lark-cli 已有的事件订阅、身份认证、消息回复与技能调用能力,把飞书入口接到本地 Codex 代理上。桥接层本身很薄,但背后的 Codex 会话可以直接调用本机已安装、已授权的 lark-* skills,而不只是回一条 IM 消息。
Codex 固定运行在一个可 attach 的 mux session 中:macOS / Linux 使用 tmux,Windows 原生使用 psmux。你可以从任意本机终端随时 attach 进去看真实界面、直接接手当前任务,也可以 detach 后让它继续后台工作。
日常使用时,你可以全程只在飞书里给 Codex 发需求、看进度、收结果,不需要盯着本地终端。这个 mux 会话更像一个随时可进入的“驾驶舱”: 平时可以不打开,需要排障、观察真实 TUI、或者临时手动接管时再进去。
- 基于
lark-cli能力栈:消息订阅、身份、回消息、技能调用都建立在现成的lark-cli/lark-*skills 之上,不额外发明一套飞书接入方式 Codex可直接操作全部飞书 skills:只要当前机器上的Codex环境里已经安装并可访问对应的lark-*skills,它就能直接调用,例如lark-im、lark-doc、lark-sheets、lark-base、lark-calendar、lark-task等- 全程飞书对话驱动:启动桥接后,日常工作流可以只在飞书里完成,需求、追问、进度同步、最终回复都走飞书线程
- 任意终端可接手同一会话:
Codex固定跑在命名的 mux session 里,你可以从任意本机终端attach到同一个会话继续操作 - 会话可后台持续运行:你可以随时
detach,桥接服务和Codex会继续在后台工作,不要求终端窗口一直挂在前台
它只做三件事:
- 用
lark-cli event +subscribe --event-types im.message.receive_v1 --compact --quiet --as bot订阅飞书消息 - 只接收指定
allowed_sender_open_id的p2p消息 - 把消息送进本地
Codex CLI会话,并要求Codex优先通过lark-im回消息;如有需要,再继续调用本机可用的其他飞书 skills 去完成文档、表格、日历、任务、知识库等操作
桥接层不会把 stdout/stderr 自动发回飞书;终端输出只用于本地日志和诊断。
bridge.py: 守护进程主脚本bridge.example.toml: 配置模板start_bridge.py: 跨平台公共启动器start_bridge.command: macOS 双击启动脚本start_bridge.cmd: Windows 双击启动脚本bin/lark-cli.ps1: Windows PowerShell 下优先命中的lark-cli包装入口bin/lark-cli.cmd: Windowscmd.exe下的lark-cli包装入口bin/lark_cli_wrapper.py:lark-cli兼容包装层,负责参数规范化和桥接增强start_bridge_powershell.cmd: Windows PowerShell 双击启动脚本feishu-codex-bridge.plist.example: macOSlaunchd示例
本机需要满足:
Python 3.11+可用(双击启动脚本会优先尝试较新的python3.x)- macOS / Linux:
tmux可用 - Windows:
psmux可用 lark-cli已安装并完成lark-cli config initCodex运行环境可以访问你希望它使用的lark-*skills- 飞书开放平台已开启长连接事件订阅
- 已添加
im.message.receive_v1 - 已开通
im:message:receive_as_bot Codex CLI可用
如果你打算让 Codex 在处理过程中读取 lark-im 与 lark-shared 技能文件,推荐把 command 配成:
codex --no-alt-screen --dangerously-bypass-approvals-and-sandbox如果你不用完全放开模式,至少要确保 Codex 能读取技能目录和 cwd 外的必要路径。
先复制模板:
cp Tools/feishu_codex_bridge/bridge.example.toml Tools/feishu_codex_bridge/bridge.toml然后填写真实值:
cwd = "/ABSOLUTE/PATH/TO/YOUR/WORKSPACE"
command = "codex --no-alt-screen --dangerously-bypass-approvals-and-sandbox"
allowed_sender_open_id = "ou_xxxxxxxxxxxxxxxxx"
message_scope = "p2p"
skill_path = "/Users/zhangyoufei/.agents/skills/lark-im/SKILL.md"
ack_text = "已收到,正在转给本地 Codex 处理,后续回复会直接回到这条消息下。"
session_idle_minutes = 30
log_path = "/ABSOLUTE/PATH/TO/feishu-codex-bridge.log"
tmux_session_name = "feishu-codex-bridge"
bridge_lark_app_id = "cli_xxxxxxxxxxxxx" # 可选,只影响桥接层监听/回执
bridge_lark_app_secret = "xxxxxxxxxxxxx" # 可选,只影响桥接层监听/回执
bridge_lark_brand = "feishu" # 可选,默认 feishu
bridge_lark_cli_profile = "my-listener-bot" # 可选;不填会自动生成桥接专用 profile字段说明:
cwd:Codex工作目录command: 原样执行的Codex启动命令allowed_sender_open_id: 只允许这个飞书用户触发message_scope: v1 固定为p2pskill_path: 注入给Codex的默认回复技能文件路径,通常填lark-im,用于明确“先通过飞书把进度和结果回出去”ack_text: 服务收到有效消息后立即回的确认文案session_idle_minutes: 会话空闲多久后自动重建log_path: 本地日志文件tmux_session_name: 长驻 mux 会话名。字段名保持不变;macOS / Linux 下对应tmux会话名,Windows 下对应psmux会话名bridge_lark_app_id: 可选。监听机器人 App ID,只给桥接守护进程外层使用bridge_lark_app_secret: 可选。监听机器人 App Secret,只给桥接守护进程外层使用bridge_lark_brand: 可选。feishu或lark,默认feishubridge_lark_cli_profile: 可选。桥接层外部监听机器人使用的 profile 名;如果同时填写了bridge_lark_app_id/bridge_lark_app_secret,桥接启动时会自动创建或更新这个 profile;如果不填,会按tmux_session_name自动生成一个桥接专用 profile
补充说明:
cwd、skill_path、log_path都支持相对路径- 相对路径会以
bridge.toml所在目录作为基准来解析 - 如果你把整个
feishu_codex_bridge文件夹打包发给别人,别人通常至少需要改这三项:cwd、allowed_sender_open_id、skill_path skill_path只是桥接启动时注入给Codex的默认沟通入口,不代表Codex只能用这一个 skill;只要同一环境里的其他lark-*skills 可用,Codex也可以直接调用- 如果你只填
bridge_lark_cli_profile,桥接层会直接用现有 profile 执行lark-cli --profile <profile> event +subscribe ... --as bot - 如果你填了
bridge_lark_app_id和bridge_lark_app_secret,桥接启动时会先自动执行一次等价于lark-cli config init --name <profile> --app-id <id> --app-secret-stdin --brand <brand>的配置写入,然后再用该 profile 监听和回执 - 上面这套 bridge 专用机器人配置只影响桥接守护进程外层;Codex 会话里之后执行的
lark-cli仍然走你本地当前默认配置,不会被桥接层改写 - 如果你不想把凭据写进
bridge.toml,仍然可以继续只填bridge_lark_cli_profile
命令行手动启动时,请显式使用本机的 Python 3.11+ 解释器;下面以 python3.11 为例,实际也可以换成 python3.12、python3.13 等。
先做环境检查:
python3.11 Tools/feishu_codex_bridge/bridge.py --config Tools/feishu_codex_bridge/bridge.toml --check再以前台方式启动:
python3.11 Tools/feishu_codex_bridge/bridge.py --config Tools/feishu_codex_bridge/bridge.toml启动后通常会先看到:
Starting Feishu event subscription
然后在订阅进程稳定约 2 秒后,你会看到这条 ready 日志:
Feishu event subscription ready (connection stable, you can now send messages from Feishu)
看到这条后,就表示当前已经过了启动窗口期,可以去飞书发送第一条消息了。
如果后面又看到:
Feishu event subscription is receiving events (first event observed)
说明这条订阅连接已经实际收到了至少一条飞书事件。
如果你希望直接双击启动:
- macOS:双击 start_bridge.command
- Windows:双击 start_bridge.cmd
- Windows PowerShell:双击 start_bridge_powershell.cmd
这两个文件都会用相对位置自动找到同目录下的 bridge.py 和 bridge.toml。
双击启动时,启动窗口默认只显示桥接服务状态日志,不显示 Codex CLI 的完整对话转录;这样窗口更适合长期挂着观察服务是否在线、订阅是否 ready、是否发生错误。
如果你需要看真实 Codex TUI、完整对话内容、或者临时手动接管,请直接 attach 当前 mux 会话,而不是依赖启动窗口:
启动后如果你想进入真实 Codex 界面,可以从任意本机终端执行:
tmux attach -t feishu-codex-bridgeWindows 原生请改用:
psmux attach -t feishu-codex-bridge如果你改了 tmux_session_name,把上面的名字换掉即可。
在 tmux / psmux 里 detach 不会停止桥接服务,也不会中断飞书侧的工作流。
如果你的 Codex 环境本来就装有完整的飞书 skills,这个桥接启动后,等于就是把“飞书私聊”直接变成了这些 skills 的远程入口:你在飞书里提需求,Codex 可以在本机直接调用 lark-cli 和对应 lark-* skills 去查文档、改表格、发消息、查日历、写任务,而结果仍然回到同一条飞书线程里。
如果你只是想关闭当前 mux 会话,但保留桥接服务继续运行:
tmux kill-session -t feishu-codex-bridgeWindows 原生对应命令是:
psmux kill-session -t feishu-codex-bridge下次再收到普通飞书消息时,桥接服务会自动重建一个新的 mux 会话。
如果桥接层检测到当前 mux 会话已经失活、pane 已死、或者实际只剩一个普通 shell 而不是活的 Codex 进程,也会直接执行同等效果的 kill-session 并重新拉起一个新的会话;Windows 下同样走 psmux。
如果你在 Windows 原生环境运行,不需要 WSL。桥接会直接使用 psmux 作为 tmux 的平替。
- 先安装
psmux:winget install psmux start_bridge.cmd启动前会先检查psmux是否可用;如果没装,会直接提示安装命令bridge.py --check在 Windows 上也会检查psmux,不会再报泛化的tmux is not available in PATHcwd、skill_path、log_path请填写 Windows 本机可访问的路径;如果你用的是 Windows 版python、codex、lark-cli,就按 Windows 路径来写- 桥接会把仓库内的
bin目录放到当前会话的PATH最前面;在 PowerShell 下会优先命中bin/lark-cli.ps1,在cmd.exe下会命中bin/lark-cli.cmd - 这个包装层会先把
--params、--data、--json规范化成稳定 JSON,再调用真实lark-cli,减少 Windows shell 对引号和编码的破坏 - 包装层还补了几条桥接增强:
docs +fetch遇到 Wiki 里的 BITABLE 会返回 handoff 信息,docs +search会为 BITABLE 结果补resolved_obj_token,base +record-get支持--fields,base +record-list支持--format object - 这些兼容和增强只保证桥接会话里的包装入口生效,不代表你系统里全局安装的上游
lark-cli原生命令已经具备同样行为
收到有效飞书消息后,桥接层会先执行:
lark-cli im +messages-reply --message-id <incoming_message_id> --text "<ack_text>" --as bot然后把消息包装成一段 envelope 送给当前 Codex 会话。启动时还会先注入一段 bootstrap prompt,明确告诉 Codex:
- 当前运行在飞书桥接模式
- 用户可见回复不要依赖终端输出
- 回复时优先使用
lark-im - 先读取
lark-shared - 对当前消息优先调用
lark-cli im +messages-reply --message-id <id> --text ... --as bot - 不要等所有回答完成才一起发,收到消息后先发一条简短进度,再在处理中持续追加回复
- 如果
Codex在 10 秒内还没有主动通过飞书回用户,桥接层会自动补一条“仍在处理中”的 watchdog 提示;之后每 30 秒补一条心跳,直到Codex真正开始通过飞书回复或任务结束 - 当前版本只处理订阅通道实时收到的消息;如果服务启动前或订阅建立窗口期有消息漏掉,桥接层不会补捞历史消息
这里的“优先使用 lark-im”是为了保证用户能先收到进度和结果,不是为了限制 Codex 的能力边界。只要你的本地 Codex 会话里可访问其他飞书 skills,它在处理任务时也可以直接继续调用这些 skill。
完整的桥接上下文不会在每条普通消息里重复注入;它只会在一段 Codex 对话的首条普通消息里发送一次。后续普通消息只会补当前 message_id、回复方式提醒和用户原文。只有当用户显式发送 /new 开启新对话,或本地 Codex 会话被重建后,下一条普通消息才会重新附带完整桥接上下文。
桥接层会自动创建并复用配置里的 tmux_session_name;如果启动时发现已经存在一个活的 Codex mux 会话,就会直接接管并继续使用,不会重复创建第二个同名会话。桥接 bootstrap 规则会按 mux 会话维度记录,复用一个已经完成 bootstrap 的会话时不会重复注入;但如果用户显式发送 /new,或者本地 Codex 会话被重建,下一轮仍会重新注入 bridge 规则。
如果会话空闲超时,服务会销毁旧 session 并在下一条消息到来时重建;如果检测到的是失活/异常会话,则会直接强制 kill 当前 session 再拉起新的 Codex。默认使用方式仍然是“人在飞书里发消息,Codex 在后台持续处理”,mux 会话只是这个后台会话的可见入口。
v1 固定行为:
- 只支持
p2p - 只允许一个
allowed_sender_open_id - 只接收
text/post两类文本可读消息 - 图片、文件、卡片等消息会收到“当前仅支持文本消息”
Codex输出只记本地日志,不自动桥接回飞书- 桥接日志会尽量压缩 TUI 噪音;完整画面请直接 attach 到当前 mux 会话
私聊机器人时,除了普通消息,还支持下面这些桥接层原生命令:
/status:立即返回桥接状态,包括当前 mux 会话是否在线、是否正在处理、排队消息数、工作目录以及 attach 命令/new:不重建 mux 会话;而是把精确匹配的/new直接送进当前Codex CLI,触发Codex自己开启新对话。之后下一条普通消息会重新附带完整桥接上下文/interrupt:立即向当前 mux 会话中的Codex发送中断信号,尝试停止当前这一轮处理,但不重建整个会话
推荐把它们理解成“桥接层保留命令”,而不是交给 Codex 解释的普通对话。
精确匹配规则:
- 只有消息内容在去掉首尾空白后,完全等于
/status、/new、/interrupt之一时,才会触发桥接层命令 - 例如
/status 现在怎么样、/new 请帮我重新开始、/interrupt 帮我停一下都不会命中保留命令 - 其他以
/开头但不精确匹配这三个命令的内容,会被当成普通用户消息,照常转发给Codex
飞书端示例:
/status
/new
/interrupt
说明:
/new会复用当前 mux 会话和当前Codex进程,只在会话内向Codex CLI发送一条/new/interrupt走立即执行路径,不排队;如果当前没有正在执行的任务,会直接回你“当前没有正在执行的任务”- 如果
/interrupt后当前这一轮仍未停止,可以直接 attach 到当前 mux 会话手动接管 /status现在会额外显示两段桥接状态:订阅进程、事件通道事件通道:连接中,正在建立事件通道表示还没 ready,建议再等一下事件通道:已就绪,当前可以在飞书发送消息表示现在可以发送第一条飞书消息事件通道:已收到事件(最近 X 秒前)表示订阅已经实际收到过消息事件
第一次启动或重启桥接后,推荐按这个顺序操作:
- 启动桥接服务
- 等待本地终端出现
Feishu event subscription ready (connection stable, you can now send messages from Feishu) - 再去飞书发送第一条普通消息,或者先发
/status - 后续默认直接在飞书里持续对话、补充需求、接收结果,不需要一直打开本地终端
- 如果想看真实界面或临时手动接手当前会话,再从任意本机终端执行 attach 命令:macOS / Linux 用
tmux attach -t <tmux_session_name>,Windows 用psmux attach -t <tmux_session_name> - 如果当前任务卡住,先发
/interrupt;还不行就直接 attach 到当前 mux 会话手动处理
如果你希望它在 macOS 上常驻,复制并修改:
cp Tools/feishu_codex_bridge/feishu-codex-bridge.plist.example ~/Library/LaunchAgents/com.example.feishu-codex-bridge.plist
launchctl load ~/Library/LaunchAgents/com.example.feishu-codex-bridge.plist记得把 ProgramArguments、WorkingDirectory、StandardOutPath、StandardErrorPath 改成你自己的绝对路径。
- 启动后完全收不到消息:优先检查飞书开放平台是否开启长连接订阅,以及
im.message.receive_v1 - 能收消息但回不了消息:检查 bot scope 和群/会话可见范围;
messages-reply需要 bot 身份 Codex无法读取技能:检查command的权限模式,确保它能访问skill_path- 日志里看到
Do you trust the contents of this directory?:首次运行时脚本会自动确认 - 日志里看到
Continue anyway? [y/N]:脚本会自动接受兼容提示,并为子进程设置TERM=xterm-256color - 想看真正的交互界面:按平台执行 attach 命令;macOS / Linux 用
tmux attach -t <tmux_session_name>,Windows 用psmux attach -t <tmux_session_name>
This project is licensed under the MIT License.