# Gitea Workflow × Codex App 集成地图 > 分析日期:2026-08-24 > > 范围:只读分析 Codex App 的 `thread/start` MCP 配置、会话创建、消息处理、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 客户端转发。因此下图的“回调边”以源码为准。 ## 端到端运行链路 ```text 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."]`,并设置 `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。 ## 建议的最小扩展边界 1. 新增独立 Gitea Workflow 服务/存储模块:Webhook 验签、delivery 去重、仓库/任务状态机、同仓库锁、全局并发上限、outbox/审计。不要把队列逻辑塞进 `handleMessage` 或 app-server 客户端。 2. 为 Codex App 调用增加“线程级 MCP 配置”参数(沿用 `mcpContext` 传递),在 `listRuntimeMcpServerConfigs` → `codexAppThreadConfig` → `codexAppThreadParams` 形成单向注入;官方 `gitea-mcp` 使用 stdio 或明确的 streamable HTTP 配置,token 只放线程 env/安全存储。 3. 将 workflow correlation(实例、owner/repo、issue/PR、评论 delivery、turn ID)写入独立任务记录,并在 turn state 中保存最小引用,保证进程重启和自动重试可恢复。 4. 在 `handleCodexAppNotification`/`handleCodexAppTurnComplete` 旁增加内部领域事件适配器:先 durable commit,再确认 Gitea 评论;缺失回执只允许一次补触发,最后 REST 兜底。 5. 后台任务通过现有 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` 的改动,本任务未触碰、未回退这些改动。