当前源码版本:v3.3.0。让 Codex 或其他代码 Agent 安装发布包时,从 Agent 安装入口 开始,并完整遵循 安装手册。
CodexLark 把飞书机器人连接到一个长期存在的 Codex Manager 会话。Manager 不挂 Goal、不执行用户任务,也不创建 Codex Subagent;它在用户批准完整配置后,通过 Codex App Server 的 thread/start 创建独立 Worker 会话。
flowchart LR
U["飞书用户 / 群聊"] --> E["lark-cli 长连接事件流"]
E --> R{"Worker 会话映射?"}
R -->|"否"| M["持久 Manager Thread"]
R -->|"私有群且 creator 匹配"| W1["Worker: alias A"]
R -->|"源群绑定话题"| V["实时查询源群当前成员"]
V -->|"验证通过"| W2["共享 Worker: alias B"]
M -->|"先发完整提案"| U
U -->|"确认创建 P-..."| M
M -->|"私聊: thread/start + 私有普通小群"| W1
M -->|"群聊: thread/start + 源群独立话题"| W2
W1 -->|"开始 -> 结果 -> 终态 / 受控进度"| U
W2 -->|"开始 -> 结果 -> 终态 / 受控进度"| U
W1 -.->|"/btw: ephemeral fork"| S["只读 Side Thread"]
S -->|"独立回答,不 steer 主 Worker"| U
- 未映射私聊消息进入 Manager Thread。任何群消息只有事件
mentions明确包含当前 bot 的真实open_id才会继续路由;私聊创建的 Worker 使用“创建者 + 机器人”的私有普通小群,只有创建者逐条@机器人后可直达;群聊创建的 Worker 使用源群独立话题,源群当前成员逐条@机器人后可直达。 - Manager 是普通可恢复 Thread,代码不会调用
thread/goal/*。 - Worker 使用全新的
thread/start,不是 fork,也不是当前会话内的 Subagent。 - 创建 Worker 前,机器人发送 alias、绝对 workspace、workspace 模式、完整 Prompt、model、reasoning effort、分支信息和有效期。
- 只有同一个飞书
open_id严格确认后才创建。私聊格式为确认创建 <proposal_id>;群聊格式为@机器人 确认创建 <proposal_id>,且必须来自原源群。校验只移除事件元数据中确认属于当前 bot 的 mention token;附加文字或@其他对象都会拒绝。 - 群 mention gate 位于会话映射、消息幂等 claim、Manager/Worker 路由和所有回复/提案副作用之前。未 @、只 @ 其他对象、纯文本伪造
@名称和 bot 自身消息均静默忽略;日志只记录 drop reason、累计计数和消息标识,不记录正文。 - alias 仍按创建者隔离,仅用于 Manager 管理。群成员在绑定话题中不需要 alias;私有群/源群话题创建失败或旧 Worker 仍可由创建者说
worker build1:增加这个要求走 Manager 回退。 - Worker 主状态只有
pending和running:Codex Thread 有进行中的 turn 时为 running,否则为 pending。两种状态都可接收新请求,状态由运行时 turn 事件维护,不由 Worker 自报。 - 每个正常 Worker Turn 有明确的三个用户可见阶段:一条开始状态、一条实质结果、一条 terminal。terminal 只承载真实结果、错误或阻塞说明,不是 Worker 状态,也不会追加第四条。
- 多步长任务的额外进度默认延迟 30 秒后才可见,后续至少间隔 60 秒且相同内容会被抑制;它不替代或重复三个固定阶段。Manager 每 30 分钟心跳唤醒 running Worker,心跳进度可立即报告;pending Worker 等待新请求。
- start/terminal 按
worker + Codex turn、result 按worker + request_message_id使用稳定飞书幂等键;同一请求或工具重试不会生成重复阶段。所有阶段进入私有 Worker 群或源群 Worker 话题,对象不可用时回退到创建时的会话。 - 通过去重、鉴权和路由校验并确定会交给 Manager/Worker 的消息,会先在原消息上显示临时
Typing表情;可见结果发送、turn 完成、失败、10 分钟超时或服务重启恢复时按reaction_id精确移除。表情 API 故障不会阻断 Agent。 - Worker 会话支持本地
/help、/worker-status、/btw、/side、/model、/effort、/interrupt和/stop。/btw [问题]与/side [问题]创建只读、临时 fork 独立回答;主 Worker 的 active turn、请求历史和状态不变。运行时向 side 注入有界的最新 plan/item/progress 快照,完成、失败或超时后清理。/model不带参数时动态列出当前 Codex catalog 里的可用模型及各自支持的 reasoning effort;设置保留同一 Codex Thread 和飞书容器,并从下一个新 turn 生效。/interrupt仅中断当前 active turn,保留 Thread、后台终端和服务;/stop仅停止当前 Worker Thread 的后台终端,不中断 turn。私聊 Worker 仍仅创建者可用;群聊创建的 Worker 中,通过实时源群成员校验的当前成员与创建者拥有完全相同的命令权限。 - 群聊严格确认后,Manager 会用普通发送创建一条全新的 Worker 信息消息,不回复确认消息,也不复用原请求;该新消息 ID 是唯一话题根。信息只展示 workspace 末级名称,不展示 Codex Thread ID、完整路径或注册表位置。
- Manager 的群聊回复和 Worker 引导会
@对应发起人;Worker 话题报告使用--reply-in-thread留在绑定话题;私聊原路回复。 - 飞书回复接口不保证同步返回
thread_id。成功回复后会话可先用精确的chat_id + root_id激活;首个真实话题事件会原子补全thread_id,源群普通消息因没有该root_id不会误投递。 - 不使用业务数据库。Manager Thread/Worker Thread 由 Codex 自身持久化;alias、提案、
chat/root/thread -> Worker Thread映射、幂等投递和状态保存在 mode0600的 JSON 注册表中。
2.1 的 Worker Conversations 状态机见 2.x 设计;3.0 的本地控制和 reaction 生命周期见 3.0 设计;3.1 的两态 Worker 与 registry v3 迁移见 3.1 设计;3.2 的 side conversation 见 3.2 设计;3.3 的 stop/interrupt 控制见 3.3 设计。
默认注册表:
<manager-workspace>/.codex-lark/manager/workers.json
- Node.js
>=22.18 - 支持 App Server 和 experimental dynamic tools 的 Codex CLI
lark-cli,并已配置可用的 bot identity- 飞书开放平台应用已启用机器人和长连接事件
im.message.receive_v1 - 应用至少具备以下权限:
im:message.p2p_msg:readonlyim:message.group_at_msg:readonlyim:message:send_as_botim:message.reactions:write_only或包含 reaction 写能力的im:message(添加并移除临时Typing表情)im:chat:create(创建私聊 Worker 的私有普通群)im:chat:update(把私有群限制为机器人群主、仅群主可加成员)im:chat.members:read(每次源群 Worker 直达前实时验证发送者仍是源群成员)
建群机器人会自动入群,并且在私聊 Worker 小群中作为群主。CodexLark 运行时始终要求所有群消息逐条 @机器人,即使应用仍拥有敏感权限 im:message.group_msg 也不会放宽。源群 Worker 每次直达还会实时分页读取当前成员,查询无法完整验证时不投递。可选生命周期监控还需 im:chat:read 及对应事件订阅;启用后会持久化群成员移除拒绝缓存,并按飞书事件时间处理重复、乱序和重新入群。
飞书官方说明:仅开通 im:message.group_at_msg:readonly 时,im.message.receive_v1 只推送群内用户 @ 当前机器人的消息;im:message.group_msg 会扩大为群内全部用户消息。可在开放平台“权限管理”中保留前者并人工移除后者,然后创建并发布新版本;仓库不能安全地替你修改已发布应用权限。无论平台是否完成收窄,运行时 mention gate 都保持启用。参见接收消息事件与API 权限列表。
npm install
npm run build
npm run doctor不设置环境变量时,当前目录就是 Manager workspace。需要自定义时,在仓库根目录创建 .env,字段参考 .env.example。CODEX_LARK_ALLOWED_WORKSPACE_ROOTS 使用系统路径分隔符连接多个根目录;Linux 上是冒号。
长期运行时建议设置 CODEX_LARK_LARK_PROFILE,将事件消费和回复固定到指定的 lark-cli Profile,避免全局 Profile 切换后串到另一个机器人。
doctor 只检查本地配置、Codex 认证、飞书 bot 认证、消息/群 API schema、可选成员事件 schema 和 App Server 模型目录,不发送飞书消息,也不启动事件消费者。
npm run supervise首次启动会创建并命名 Manager Thread,以后从注册表恢复同一个 Thread。Supervisor 和 Service 分别使用单实例锁;Service 意外退出时,Supervisor 会以 1 秒到 30 秒的指数退避自动拉起。Supervisor 收到 SIGINT 或 SIGTERM 时会把信号传给 Service,有序关闭飞书事件连接和 Codex App Server,不再重启。
npm start 仍可用于前台调试单个 Service,但不会在整个 Service 退出后自动拉起。
Service 的 stdout/stderr 默认持久写入:
<manager-workspace>/.codex-lark/manager/service.log
日志达到 10 MiB 时轮转,默认保留 service.log.1 到 service.log.5。路径、大小、份数和重启退避可通过 .env 中的 CODEX_LARK_LOG_*、CODEX_LARK_RESTART_* 调整。日志和锁文件使用 mode 0600。
建议首次联调按以下顺序进行:
- 私聊机器人发送一个简单开发任务。
- 检查机器人返回的完整 Worker 提案。
- 私聊原样回复
确认创建 P-...;群聊原样发送@机器人 确认创建 P-...。 - 私聊创建时,确认收到独立私有普通小群和首条引导;一个正常短任务应严格显示 start、result、terminal 三条 Worker 消息且每阶段恰好一条。
- 群聊创建时,确认源群先出现一条全新的 Worker 信息根消息(不是原请求/确认的 reply),随后形成独立话题;让另一名当前群成员在话题内用
@机器人追加任务并发送@机器人 /worker-status。 - 在 Worker 会话发送
@机器人 /help和@机器人 /model,确认返回命令帮助、动态模型与推理强度列表;主任务运行时发送@机器人 /btw 现在做到哪一步了?,确认收到独立回答且主任务未中断。分别验证/interrupt只中断 active turn、/stop只清理后台终端;在源群 Worker 话题中,创建者和另一名当前群成员都应能修改设置和使用停止命令,离群成员必须被拒绝。 - 在私有群或源群话题用
@机器人发送新增要求,确认原消息立即出现Typing,可见结果后移除,并验证 pending Worker 恢复原 Thread、进入 running,turn 完成后自动回到 pending。 - 临时关闭
CODEX_LARK_WORKER_CONVERSATIONS_ENABLED、使用不支持话题回复的群,或使用缺少建群权限的测试 Profile,验证 Manager alias 回退。
群聊测试时,每条需要机器人接收的消息都必须 @机器人,与飞书是否仍投递全量群消息无关;群聊 Worker 的回复始终留在绑定的源群话题。
Manager 会在提案中选择以下一种方式:
existing:使用已存在目录。创建前会解析realpath并验证没有越过允许根目录。git-worktree:用户批准后才运行git worktree add。目标必须位于CODEX_LARK_WORKTREE_ROOT,创建前会检查父目录符号链接。Manager workspace 必须是 Git 仓库。
当前目录若不是 Git 仓库,只能使用 existing,或先由人工初始化仓库。Worker 不暴露权限配置项,运行时固定为 danger-full-access 和 approval_policy=never;安全边界是创建前的完整飞书提案审批。部署给更多用户前,应另外限制机器人可见范围和宿主机权限。
npm test
npm run test:integration普通测试完全使用假飞书客户端。integration 测试会创建只读、ephemeral 的独立 Codex Thread,验证 dynamic tool 往返和实验性 App Server 协议,不访问飞书。
给另一台机器部署时先阅读 INSTALL_FOR_CODEX.md。发布包使用源码白名单生成,不包含 .env、.codex-lark/、node_modules/、日志、CLI 凭据或 Codex Thread 状态:
npm run package:release产物和外部 SHA-256 文件写入 release/;归档内另有 SHA256SUMS,用于校验每一个文件。
deploy/codex-lark.service.example 是 systemd 模板。把其中的绝对路径替换为实际 Node、仓库和环境文件路径后,以运行 Codex/lark-cli 凭据的同一用户启动。模板托管 Supervisor;Supervisor 再负责 Service 的自动拉起和持久日志。不要以多个实例消费同一个 im.message.receive_v1 事件流。
- Dynamic tools 属于 Codex App Server experimental API;升级 Codex CLI 后应先运行
doctor和 integration 测试。 - Side conversation 依赖 experimental
thread/fork、thread/inject_items和thread/unsubscribe。默认 5 分钟超时由CODEX_LARK_SIDE_TIMEOUT_MS控制;每个 Worker 同时只运行一个 side,且 side 永远使用只读 sandbox。 - 30 分钟心跳按进程时钟触发。普通进度的首条延迟与节流分别由
CODEX_LARK_WORKER_PROGRESS_INITIAL_DELAY_MS和CODEX_LARK_WORKER_PROGRESS_THROTTLE_MS控制。消息会 steer 到忙碌 Turn,但模型只有到达新的输入边界后才能执行报告工具,因此极端长时间的不可中断工具调用可能延迟报告。 - “永不结束”指 Thread 持久并可持续恢复,不代表单个 Turn 永不结束。Codex 仍会按自身机制做上下文压缩。
- 注册表不是外部服务,但包含用户 open_id、Prompt 和消息路由,应保持文件权限并纳入主机备份策略。