Files
cc-web/.planning/2026-08-23-gitea-workflow/integration-map.md

18 KiB
Raw Blame History

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 客户端转发。因此下图的“回调边”以源码为准。

端到端运行链路

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。

建议的最小扩展边界

  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 的改动,本任务未触碰、未回退这些改动。