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

149 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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