18 KiB
18 KiB
Gitea Workflow × Codex App 集成地图
分析日期:2026-08-24
范围:只读分析 Codex App 的
thread/startMCP 配置、会话创建、消息处理、turn 完成、后台运行、重启恢复和现有回归入口。未修改server.js、lib/、public/。
证据与方法
- codebase-memory 项目:
home-cc-web,list_projects返回根目录/home/cc-web,节点 6670、边 15041;index_status为ready。 - 使用
get_architecture(aspects=["all"])确认server、codex-app-runtime、regression、codex等模块边界。 - 使用
search_graph、get_code_snippet、trace_path(mode="calls")定位函数和调用关系;再以rg -n/nl -ba交叉核对行号。 - 图谱无法把回调赋值完整表示出来:
handleCodexAppNotification的调用者列表为空,但源码显示它在getCodexAppClient()作为onNotification回调传入客户端,再由 JSONL 客户端转发。因此下图的“回调边”以源码为准。
端到端运行链路
WebSocket message (server.js:7706-7766)
-> handleMessage (server.js:9852-10190)
-> handleCodexAppMessage (server.js:11888-11960)
-> activeCodexAppTurns + persistCodexAppTurnState(immediate)
-> startCodexAppTurn (server.js:11962-12022)
-> getCodexAppClient (server.js:11782-11845)
-> createCodexAppServerClient (lib/codex-app-server-client.js:6-223)
initialize -> initialized -> postInitialize
-> thread/start 或 thread/resume (threadParams)
-> codexAppThreadConfig -> mcp_servers.* / web_search
-> turn/start (collaborationMode)
-> app-server stdout JSONL
-> client.handleMessage (lib/codex-app-server-client.js:78-111)
-> onNotification(handleCodexAppNotification)
-> findCodexAppRouteByRuntime (server.js:11050-11066)
-> codexAppRuntime.processCodexAppNotification (lib/codex-app-runtime.js:865-1040)
-> persist state; turn/completed -> handleCodexAppTurnComplete
1. 线程级 MCP 配置和环境
| 位置 | 行号 | 当前行为 | Gitea 扩展建议 |
|---|---|---|---|
server.js buildCcwebMcpRuntimeConfig |
2709-2753 | 生成内置 ccweb MCP;HTTP 模式把来源会话/跳数放入 URL,stdio 模式生成命令及 env。 |
Gitea MCP 应作为同级线程配置注入,不要改 app-server 进程全局 env。 |
server.js listRuntimeMcpServerConfigs |
2755-2772 | 合并项目 .codex/config.toml MCP 与内置 ccweb,按规范化 server 名去重。 |
增加 workflow 专属配置的明确入口(建议由 mcpContext/工作流配置传入),并定义与项目同名时的优先级。 |
server.js codexAppCcwebMcpEnv |
11680-11691 | 输出 CC_WEB_SOURCE_SESSION_ID、CC_WEB_CROSS_HOP_COUNT、内部 token 等。 |
Gitea 仓库/任务 ID 应随 thread/start.config.mcp_servers.gitea.env 下发;不要写入 buildCodexAppClientSpec().env。 |
server.js codexAppThreadConfig |
11693-11702 | 把 runtime 配置映射为 config["mcp_servers.<server>"],并设置 web_search。 |
这是最直接的 gitea-mcp 注入扩展点;应保证每个线程拿到独立 host/token/workflow 上下文。 |
server.js codexAppThreadParams |
11857-11868 | 组装 cwd、model、权限和 config,用于 start/resume。 |
Gitea 任务必须从这里同时覆盖 thread/start 和 thread/resume,避免重试/恢复丢 MCP。 |
server.js buildCodexAppClientSpec |
11735-11780 | 构造单例 app-server 进程的 command/args/env/signature;会剥离敏感环境变量。 | 不要把仓库 token 放此处;若新增全局配置,需纳入 signature 并评估活跃 turn 的复用策略。 |
当前 codexAppCcwebMcpEnv 已证明“来源上下文线程级下发”可行。Gitea 方案应沿用这一模式,例如 mcp_servers.gitea 的 command/args/env 或 streamable HTTP 配置,而不是 dynamic tools。
2. 会话创建、turn 启动和协作参数
| 位置 | 行号 | 当前行为 | Gitea 扩展建议 |
|---|---|---|---|
server.js WebSocket 分发 |
7706-7766 | message 进入 handleMessage;slash 先经 handleSlashCommand。 |
Webhook 不应伪造 WebSocket;工作流服务可调用同一内部处理函数或抽取“创建/投递消息”服务层。 |
server.js handleMessage |
9852-10190 | 校验输入、创建/加载 JSON 会话、持久化用户消息;Codex App 分支调用 handleCodexAppMessage。ws 可为 null,wsSend 会安全忽略。 |
后台 Gitea 任务可复用会话创建逻辑,但应显式传 workflow 元数据、固定 agent=codexapp/yolo,并避免前端 slash 语义污染。 |
server.js handleCodexAppMessage |
11888-11960 | 建立 activeCodexAppTurns entry,记录 mcpContext/retry 信息,立即写 state,然后异步启动 turn。 |
在 entry/retry state 中保留不可变的 Gitea workflow/task/repository correlation;该信息不能只存在内存。 |
server.js startCodexAppTurn |
11962-12022 | 复用单例客户端;有 threadId 则 resume,否则 start;校验恢复线程 ID;保存 runtime thread ID;发送 turn/start。 | Gitea 首次任务走 start,后续评论/重试走同一 thread resume;将 gitea MCP 配置传入 codexAppThreadParams,并在 mismatch 时按任务失败处理。 |
server.js codexAppCollaborationMode / codexAppTurnParams |
11704-11712、11870-11886 | collaboration mode 总是存在,plan 映射 plan,否则 default;model/reasoning_effort/developer instructions 放 settings,顶层不重复 model/effort。 |
后台固定 yolo 会得到 mode=default;不要为 Gitea 任务再传顶层 model/effort,避免回到非原生协作路径。 |
server.js codexAppPostInitialize |
11714-11733 | initialize 后 best-effort 探测 goals 和 collaborationMode/list,失败只记日志。 |
Gitea 集成不应依赖探测成功;可在能力缺失时记录降级并继续普通 turn。 |
3. JSON-RPC 客户端、通知、服务端请求
| 位置 | 行号 | 当前行为 | Gitea 扩展建议 |
|---|---|---|---|
lib/codex-app-server-client.js handleMessage |
78-111 | 以 id 区分 pending response/server request;无 id 的 method 交给 onNotification。 |
回执监听应基于 turn/thread ID,不要解析 UI 文本;保留未知 method 日志。 |
lib/codex-app-server-client.js start |
140-190 | 启动 stdio app-server,绑定 readline,initialize/initialized,再运行 postInitialize;退出 reject 所有 pending。 |
gitea-mcp 子进程由 app-server 按线程配置管理;不要另起一套 app-server 连接。 |
server.js getCodexAppClient |
11782-11845 | 全局单例 codexAppClient;配置 signature 变化时若有其他活跃 turn 会复用旧客户端,否则失败残留并重启。 |
队列不能通过频繁改全局 config 切换仓库;每个任务使用线程 config,避免触发 singleton stale-config 分支。 |
server.js handleCodexAppNotification |
11105-11155 | 先处理 compact/Goal/MCP startup,再按 parent/child/磁盘恢复路由;runtime 返回 done 时进入完成处理。 | Gitea 回执监听应挂在此处或其下游的领域事件,而不是监听 WebSocket done;需识别 turn/completed 的 status/error。 |
server.js handleCodexAppServerRequest |
11572-11608 | 处理 approval、user input、dynamic tool;未知请求按保守策略拒绝。 | gitea-mcp 是 MCP server,不应新增 dynamic tool 旁路;若需要用户确认,明确映射到工作流 waiting_user。 |
server.js handleCodexAppServerExit / handleCodexAppTurnFailure |
11610-11637、12165-12170 | app-server 退出会使所有 active Codex App turn 失败;失败统一进入完成清理和重试判定。 | Gitea 任务需把此类失败映射为可恢复状态,防止一次 singleton 退出同时丢失多个仓库任务。 |
4. turn 完成、后台运行与持久化恢复
| 位置 | 行号 | 当前行为 | Gitea 扩展建议 |
|---|---|---|---|
server.js handleCodexAppTurnComplete |
12024-12163 | 去重并持久化 assistant/toolCalls;处理 transient retry;删除 active entry、清理 run dir;有 ws 发 done,无 ws 则广播 background_done 并调用通知。 |
在删除 entry/清理目录前写入 workflow completion/outbox;回执确认、补触发和 REST 兜底必须幂等。建议发领域事件,避免依赖 sendNotification。 |
server.js handleCodexAppSteerMessage |
12189-12419 | 运行中用 turn/steer;遇到 no-active-turn 会先收敛旧输出,再复用消息启动新 turn。 |
Gitea 连续评论可转为 steer,但要用任务级 message/delivery ID 去重,避免 steer fallback 重复修改仓库。 |
server.js handleCodexAppAbortSession |
12421-12451 | 发送 turn/interrupt,超时后强制完成;无可用 client 直接以 interrupted 完成。 |
工作流取消/仓库停用可复用,但状态机需区分用户取消、运维中止、app-server 崩溃。 |
server.js writeCodexAppTurnState / persistCodexAppTurnState / loadCodexAppTurnState |
5039-5059、5061-5082、5093-5116 | state 原子写入、延迟 flush、大小保护;序列化当前 turn 文本/toolCalls/usage/error。 | 必须把 workflow/task/repository correlation 和回执状态纳入独立持久化(或扩展 state schema),不能只依赖 entry.mcpContext。 |
server.js recoverCodexAppTurnState |
5137-5226 | 重启后恢复 partial assistant/toolCalls,写 interrupted system message,清理 run dir;不自动继续 turn。 | queued/waiting_user/running 的 Gitea 状态恢复应在工作流队列层实现;running 只能标为 interrupted 后按策略最多重试一次。 |
server.js recoverProcesses + 启动调用 |
7398-7490、12950 | 启动扫描 *-run;Codex App state 优先走 recoverCodexAppTurnState,普通进程走 JSONL tail/replay。 |
工作流恢复入口应在此生命周期之后加载 durable queue,再决定是否重新投递;不要在 recoverProcesses 中直接执行 Gitea 网络副作用。 |
server.js updateSessionRuntimeThreadIndex / saveSession |
3443-3456、4497-4520 | 保存会话时维护 thread→session O(1) 索引;未知线程有负缓存,避免通知热路径反复扫盘。 | Gitea 任务必须持久化稳定 session/thread 映射;删除仓库/会话时同步清理映射,避免跨仓库通知串线。 |
server.js findCodexAppRouteByRuntime |
11050-11066 | parent active entry → 已知 child thread → 磁盘 adopt;child 先于 parent adoption。 | Gitea MCP 可能产生 child/collab activity,工作流回执只认 parent turn;需保留 child 事件但禁止把 child completion 当最终回执。 |
无 WebSocket 的后台路径是可行的:handleMessage(null, ...) 的 wsSend 为空安全,handleCodexAppTurnComplete 会走 background_done/通知分支。但该分支目前没有 Gitea 专属完成回调,必须增加可持久化的领域事件或 outbox。
建议的最小扩展边界
- 新增独立 Gitea Workflow 服务/存储模块:Webhook 验签、delivery 去重、仓库/任务状态机、同仓库锁、全局并发上限、outbox/审计。不要把队列逻辑塞进
handleMessage或 app-server 客户端。 - 为 Codex App 调用增加“线程级 MCP 配置”参数(沿用
mcpContext传递),在listRuntimeMcpServerConfigs→codexAppThreadConfig→codexAppThreadParams形成单向注入;官方gitea-mcp使用 stdio 或明确的 streamable HTTP 配置,token 只放线程 env/安全存储。 - 将 workflow correlation(实例、owner/repo、issue/PR、评论 delivery、turn ID)写入独立任务记录,并在 turn state 中保存最小引用,保证进程重启和自动重试可恢复。
- 在
handleCodexAppNotification/handleCodexAppTurnComplete旁增加内部领域事件适配器:先 durable commit,再确认 Gitea 评论;缺失回执只允许一次补触发,最后 REST 兜底。 - 后台任务通过现有 Codex App 单例运行,但由 Gitea 队列决定何时调用;不要修改
buildCodexAppClientSpec的全局环境来切换仓库。
潜在冲突与风险
- 单例 app-server 与多仓库并发:
getCodexAppClient只有一个 client,退出会影响全部 active turns;队列必须在 client 层之上做并发/重试隔离。 - 线程配置与进程环境边界:
buildCodexAppClientSpec的 env 是进程级,不能承载某个仓库的 Gitea token;否则并发任务会串凭据。 - 恢复丢失 MCP 上下文:
adoptCodexAppUnroutedTurn(server.js:10379-10423)新建 entry 时mcpContext: {};recoverCodexAppTurnState也只恢复输出,不恢复 workflow 上下文。必须把上下文放独立任务记录或 state schema。 - 完成清理时序:
handleCodexAppTurnComplete在 12087-12088 删除 active entry/清理 run dir,之后才通知后台客户端;回执 outbox 必须在清理前落盘。 - 自动重试副作用:
shouldRetryCodexTransientFailure路径会重新启动 turn;若模型已修改仓库或已发评论,需用任务/评论/turn 幂等键防止重复写入。 - 通知路由与未知线程:当前有 O(1) index、磁盘 adoption 和负缓存;Gitea 不能只依赖内存 active map,否则重启后通知会变成 unrouted。
- 现有运行保护:
handleMessage会拒绝 active turn/Goal/compaction;工作流必须先做同仓库串行和取消策略,不能靠重复调用绕过保护。 - 协作模式形状:启用 collaborationMode 时顶层不得重复
model/effort;gitea workflow 的 yolo 固定策略应保持mode=default+ settings。 - 安全边界:Webhook HMAC、Bot 自评论过滤、delivery 去重、token 脱敏、remote URL 不落 token,需要在 Gitea 层完成,不能依赖 Codex App MCP 工具自行保证。
必须补的测试
Codex App 协议/单元回归
thread/start首次任务与thread/resume重试都包含mcp_servers.gitea,且每个 session 的 host/token/workflow env 隔离;项目 MCP、ccweb MCP、gitea MCP 同时存在时不丢失/不误去重。- collaborationMode 仍把 model/reasoning_effort/developer instructions 放在
settings,顶层没有重复 model/effort;yolo 固定为 default。 handleMessage(null, ...)能创建后台 Codex App 会话;无 ws 完成时仍持久化 assistant/toolCalls,并触发可断言的 workflow completion/outbox,而不是只依赖 push notification。- turn state 原子写入/重启恢复包含 workflow correlation;恢复后 parent thread、gitea MCP 配置和回执状态不丢失;未知线程 adoption 不得把 child 事件当 parent 最终完成。
- app-server 退出、transient capacity/reconnect、thread resume mismatch、steer no-active-turn 均只产生一次工作流状态转移和一次回执动作。
Gitea Workflow 业务回归
- Webhook HMAC 正确/错误、重复 delivery、Bot 自评论、非 Issue/PR 普通评论过滤。
- 首次合法触发自动接入仓库、clone/fetch 失败恢复、同仓库串行、不同仓库并行及全局并发上限。
- 评论→session 映射稳定(
gitea + owner/repo + type + number),连续评论 steer/resume 不重复用户消息或仓库修改。 - turn 完成后 MCP 回帖确认:正常一次;缺失时仅一次“只补回执”隐藏 turn;再次失败才 REST 兜底;重启/重复 webhook 不重复评论。
- running 中断、waiting_user 保留、queued 恢复、仓库暂停/全局暂停/取消排队/中止 turn 的状态机和审计日志。
- token 不出现在日志、错误消息、session JSON、git remote URL;Webhook 重放和跨仓库 thread/child 通知不能串任务。
现有可复用测试入口
| 入口 | 行号 | 已覆盖内容 |
|---|---|---|
scripts/regression.js assertCodexAppRuntimeSubAgentActivityContract |
2276-2500 | runtime 通知到工具/子代理卡片的归一化。 |
scripts/regression.js assertCodexAppTransientReconnectContract |
2501-2542 | reconnect 进度不结束 turn,终态错误结束 turn。 |
scripts/regression.js assertCodexAppStaleRunningRecoveryContract |
4038-4058 | stale steer 替换 turn 的持久化/flush 约束。 |
scripts/regression.js runCodexAppRuntimeImageSteerRegression |
4233-4365 | 后台/运行中 steer 真实 WebSocket 流程。 |
scripts/regression.js runCodexAppStaleRunningRegression |
4367-4558 | app-server 缺失终态通知后的恢复及消息去重。 |
scripts/regression.js assertCodexAppChildToolRoutingContract |
4867-5074 | child thread 与父卡片路由隔离。 |
scripts/regression.js assertCodexAppUnroutedNotificationRoutingContract |
5076-5113 | thread 索引、负缓存、磁盘 adoption。 |
scripts/regression.js main --target |
6032-6173 | 可增加 gitea-workflow-codexapp 独立目标,避免每次跑完整回归。 |
scripts/regression.js Codex App 集成段 |
7239-7622 | mock app-server 下的 session、MCP config、collaboration、retry、Goal、runtime warning、工具调用。 |
scripts/mock-codex-app-server.js ensureThread/completeMcpToolTurn/startTurn/handleRequest |
91-114、670-737、863-1023、1066-1250 | 可扩展为断言 gitea MCP 配置、thread/start/resume 和 turn/completed。 |
package.json regression script |
8 | 统一执行 node scripts/regression.js。 |
建议新增 mock 行为:在 completeMcpToolTurn 输出 gitea server 的 type/command/args/env(token 脱敏),在 thread/start/thread/resume 记录 config fingerprint;新增回归目标仅断言协议形状和状态机,不调用真实 Gitea。
当前验证结果
node scripts/regression.js --target codexapp-retry-runtime:通过。node scripts/regression.js --target codexapp-unrouted-routing:通过。node scripts/regression.js --target codexapp-stale-running:通过。- 本任务没有修改
server.js、lib/或public/;仅新增本分析文件(以及临时计划 CSV,完成后删除)。校验时工作区已存在其他并行任务对server.js/lib的改动,本任务未触碰、未回退这些改动。