# Gitea Webhook 驱动 ccweb 工作流 PRD > 文档状态:已确认需求基线(MVP) > 适用范围:单个 Gitea 实例、一个全局 `ccweb-bot` 账号 > 相关设计:[ARCHITECTURE.md](./ARCHITECTURE.md) ## 1. 背景与目标 本功能把 Gitea Issue/PR 的普通评论作为 ccweb 的任务入口。用户在评论中 提及 `@ccweb-bot` 后,cc-web 验证 Webhook、准备持久工作区、复用该 Issue/PR 的独立 Codex App 会话,并通过官方 `gitea-mcp` 完成代码研究、修改 和回帖。Gitea 是唯一主交互入口;cc-web 页面只提供镜像、运维和审计能力。 目标: 1. 让用户无需离开 Gitea 即可驱动一次或连续多轮工程任务。 2. 保证同一仓库的工作区和任务严格串行,不同仓库可并行;MVP 不设置额外的 全局并发上限。 3. 在进程重启、网络抖动、MCP 回帖失败时,任务状态、会话和回执仍可恢复、 重试且不重复执行或重复回帖。 4. 对 Webhook、Bot Token、工作区写入和每次工具调用提供可追溯审计,并允许运 维人员暂停、取消排队和中止运行中的 turn。 ## 2. 已确认决策 | 主题 | 决策 | |---|---| | Gitea 实例 | MVP 只支持一个配置的 Gitea 实例,`instanceId=default` | | Bot | 一个全局 `ccweb-bot`,用于触发识别、起始回执和 REST 兜底 | | 触发 | Issue 或 PR 的普通评论正文包含 `@ccweb-bot`;评论作者不能是 Bot 自身 | | 自动接入 | 合法触发首次发现未登记仓库时自动登记并 clone;管理员可停用 | | 工作区 | 可配置工作区根目录下按仓库持久 checkout,后续使用 fetch 更新 | | 并发 | 同仓库串行;跨仓库并行;MVP 不设置全局运行上限 | | Agent | 固定 Codex App(`codexapp`)+ `yolo`,不在 Gitea 交互中暴露模式切换 | | MCP | 官方 `gitea-mcp`,stdio 传输;线程级注入 Gitea host/token | | 会话 | 每个 Issue/PR 一个独立持久会话;后续同资源评论进入同一会话 | | 交互 | Gitea 为主;cc-web 页面只展示状态、队列和审计,不替代 Gitea 对话 | | 回复 | 先发一条“已收到”回执;正常最终答案由 gitea-mcp 发回原 Issue/PR;缺失时一次补触发,最后 REST 兜底 | | 安全 | Webhook HMAC、delivery 去重、Bot 自评论过滤、Token/Secret 分离、审计 | | 恢复 | `queued` 重启后继续;`waiting_user` 保留;`running` 视为中断并最多重试一次 | | 运维 | 全局暂停、仓库停用、取消排队、中止 turn、查看审计 | ## 3. 范围 ### 3.1 In Scope - Gitea Webhook 接收、原始请求 HMAC 校验和 delivery 幂等。 - Issue/PR 普通评论事件的规范化、提及解析和 Bot 自评论抑制。 - 未登记仓库自动接入、持久 clone/fetch、脏工作区保护。 - 任务队列、同仓库锁、跨仓库并行。 - Issue/PR 独立会话与 Codex App turn 生命周期管理。 - 线程级官方 `gitea-mcp` stdio 配置和 Gitea 主交互。 - 起始“已收到”回执、MCP 最终回帖确认、一次补触发和 REST 兜底;不发送运行中、等待用户或成功状态评论。 - 暂停、停用、取消排队、中止、重启恢复、审计和可观测字段。 - 可供前端/运维使用的只读状态和控制接口契约(实现不在本 PRD 变更范围)。 ### 3.2 Out of Scope - 多 Gitea 实例、跨 Forge 统一协议和 GitHub/GitLab 适配。 - 非评论触发(Push、定时任务、Issue 创建、Actions 等)。 - 让普通用户在 cc-web 中切换模型、推理强度或 yolo/plan 模式。 - 自定义 dynamicTools 代替 MCP;手动填写 MCP 工具参数的 UI。 - 自动清理用户未提交修改、`reset --hard`、强制覆盖其他会话的工作区。 - 细粒度按用户/路径的写权限策略(Bot 全写权限是已确认前提);只记录风险并提供暂停/审计。 ## 4. 角色与核心场景 | 角色 | 能力 | |---|---| | Gitea 用户 | 在 Issue/PR 评论 `@ccweb-bot`,查看状态、追问、确认结果 | | `ccweb-bot` | 发送起始回执、作为 MCP/REST 身份回帖;Bot 自己的评论不会再次触发 | | 运维管理员 | 全局暂停/恢复、仓库停用/启用、取消排队、中止 turn、查审计 | | 系统 | 验签、去重、排队、clone/fetch、启动会话、确认回帖、重试和恢复 | 典型链路: ```text Gitea 普通评论 @ccweb-bot │ ▼ 验签 → delivery 去重 → 解析 Issue/PR → 自动接入仓库 │ │ └────────────── 持久化任务并入队 ◄──┘ │ 同仓库锁串行,跨仓并行 ────┘ ▼ Codex App/yolo + gitea-mcp(stdio) │ 已收到回执 → MCP 最终回帖 → turnId/评论确认 │ 补触发一次 → REST 兜底(必要时) ``` ## 5. 功能需求 ### FR-01 触发与事件规范化 - 接受 Gitea 配置的 Issue/PR 评论 Webhook;事件适配器将不同版本的事件名称 归一为 `issue_comment` 或 `pull_request_comment`。 - 仅当正文包含独立 mention `@ccweb-bot` 时触发,mention 后的文本作为任务 指令;没有有效指令时仍创建可追踪任务并要求用户补充。 - 记录原始 `deliveryId`、事件类型、仓库、资源类型/编号、评论 ID、作者和 接收时间。无法解析的事件进入 `ignored`,不启动 Agent。 - Bot 自己发出的状态/最终/兜底评论必须被识别并忽略,防止自触发环路。 ### FR-02 Webhook 安全与幂等 - 在读取或持久化业务字段前,使用原始 body 和配置 Secret 计算 HMAC-SHA256, 采用常量时间比较;验签失败返回 401,不入队。 - `deliveryKey = instanceId + ":" + deliveryId` 唯一;已处理或处理中重复 delivery 返回 200(或约定的幂等响应)但不创建新任务。 - Secret、Bot Token、MCP 启动参数不得写入评论、普通日志或审计明文;审计只 记录凭据引用/哈希指纹。 ### FR-03 仓库自动接入与工作区 - 通过合法 Webhook 首次发现仓库时原子创建 `RepositoryRecord`;未登记不因 任务排队失败而丢失事件。 - 按 `workspaceRoot///` 建立持久目录。首次使用 `clone`,后续任务在仓库锁内 `fetch`;凭据通过临时环境或 credential helper 注入,不把 Token 写入 remote URL。 - 检测到未提交修改、冲突或目录被外部占用时,不执行 reset/clean/覆盖;任务 进入 `blocked_workspace`,写入审计并等待管理员处理。 - PR 任务必须记录 base/head ref 和源仓库信息;Issue 任务使用仓库默认分支, 除非指令明确指定已允许的分支。 ### FR-04 队列、锁与并发 - 调度器维护持久任务队列;同 `repoKey` 同时最多一个 `preparing/running` 任务,跨仓库可并行。 - 不设置额外全局运行信号量;`queued` 任务只受全局暂停和各自仓库锁影响,暂停时不再领取新任务。 - 队列顺序默认 FIFO;同一资源的新评论可合并为下一轮输入,但不得跳过已持久 化的任务或破坏评论顺序。 - 取消排队只允许作用于尚未启动的任务;中止操作必须向 Codex App 发送取消, 并等待 `aborted` 或超时后释放锁。 ### FR-05 会话与 Agent 运行时 - `sessionKey = instanceId:owner/repo:kind:number`,其中 `kind` 为 `issue` 或 `pull_request`;同一资源永远复用同一个 `threadId`。 - 每个新任务创建一个 `turnId`;持久化 `threadId/turnId`、启动时间、最后事件 序号和重试次数,支持断线后恢复监听。 - 启动参数固定为 `codexapp` 与 `yolo`。`model`、`reasoning_effort`、 `developer_instructions` 放入 `collaborationMode.settings`(若运行时启用 collaborationMode),不得在顶层重复传递。 - `thread/start.config.mcp_servers.gitea` 线程级注入官方 gitea-mcp stdio;每 个线程使用对应实例的 host/token,不使用进程全局来源会话变量替代。 - Prompt 必须包含仓库/资源标识、评论上下文、工作区路径、预期回帖格式和安全 边界;评论正文视为不可信输入,不得覆盖系统约束。 ### FR-06 Gitea 交互与回帖确认 - 合法任务只发送一条起始 `giteabot: 已收到` 回执;运行中、等待用户和成功状态 不再额外发送评论,避免污染 Issue/PR 讨论串。回执仍带不可见关联标记 (`taskId`、`state`、可选 `turnId`)用于去重和审计。 - 正常最终答案必须由 gitea-mcp 的 Issue/PR comment 工具发送;cc-web 记录 MCP 返回的 `commentId` 或工具调用结果,并通过 Gitea 查询确认实际存在。 - 确认必须同时匹配 `resourceKey`、`taskId`、`turnId`(或等价不可见标记)和 Bot 作者,避免把旧评论当作本轮回执。 - 仅在确认 Gitea 评论查询结果为“缺失”时,发起一次“只补发回执、不重复修改”的 隐藏 turn;补发仍缺失才以 Bot Token 经 Gitea REST 发布最终正文。查询结果为 `unknown` 时不自动重试或覆盖,任务进入 `failed_reply`,由终态兜底回执告知用户, 避免把已经成功落库的 MCP 回复重复发布。 ### FR-07 重试、恢复与状态一致性 - 网络/进程级暂时错误使用持久 `retry_wait` 和指数退避;执行 turn 因重启被 中断时最多自动重试一次,禁止无界重试。 - `queued` 重启后恢复调度;`waiting_user` 保留原会话等待新评论;`running` 恢复为 `retry_wait` 并带 `interrupted_by_restart` 原因;终态不重新执行。 - 所有状态变更、评论发送、MCP 工具调用和重试都追加审计事件,状态写入遵循 版本号/单调序列,防止旧事件覆盖新状态。 ### FR-08 运维控制与审计 - 全局 `pause/resume`:暂停只阻止新任务领取,已运行任务继续或由管理员另行中止。 - 仓库 `disable/enable`:停用后拒绝该仓库的新触发并保留历史;正在运行的任务 不强制杀死。 - `cancel queued`、`abort running turn` 必须幂等,响应包含当前状态和操作者。 - 审计事件至少包含 `eventId、taskId、sessionKey、actor、action、fromState、 toState、deliveryId、turnId、commentId、errorCode、timestamp`;支持按仓库、 资源、任务和时间范围查询。 ## 6. 状态机 ### 6.1 任务状态 | 状态 | 含义 | 可转移 | |---|---|---| | `received` | 已验签并完成事件规范化 | `queued`、`ignored`、`duplicate`、`rejected` | | `queued` | 等待仓库锁和全局槽位 | `preparing`、`cancelled` | | `preparing` | 正在登记仓库、clone/fetch、构造线程 | `running`、`blocked_workspace`、`retry_wait`、`failed` | | `running` | Codex App turn 执行中 | `waiting_user`、`verifying_reply`、`retry_wait`、`aborting`、`failed` | | `waiting_user` | Agent 需要用户补充,保留会话 | `queued`(新评论)、`cancelled`、`failed` | | `verifying_reply` | 已收到 turn 完成,等待确认最终评论 | `succeeded`、`retry_wait`、`failed_reply` | | `retry_wait` | 到达退避时间,等待有限次重试 | `preparing`、`running`、`verifying_reply`、`failed` | | `blocked_workspace` | 工作区脏/冲突/权限问题 | `queued`(管理员解除后)、`cancelled` | | `aborting` | 已请求取消,等待 Codex App 确认 | `aborted`、`failed` | | `succeeded` | 最终回帖已确认 | 终态 | | `succeeded_with_rest_fallback` | 使用 REST 兜底回帖成功 | 终态 | | `failed` / `failed_reply` | 执行或回帖不可恢复失败 | 终态 | | `cancelled` / `aborted` | 排队取消或运行中止 | 终态 | | `ignored` / `duplicate` / `rejected` | 未触发、重复或安全拒绝 | 终态 | ### 6.2 状态不变量 1. 只有 `preparing/running/aborting` 持有仓库锁;同一 `repoKey` 同时最多一个 运行中任务。 2. 终态任务不得创建新 turn、状态评论或重试;重复 Webhook 只能返回原任务。 3. `verifying_reply` 必须引用本轮 `turnId`;`failed_reply` 不代表代码执行必然 失败,必须在审计中区分 `executionError` 与 `replyError`。 4. `waiting_user` 的新评论创建新 `taskId`,但复用同一 `sessionKey/threadId`。 ## 7. 非功能需求与风险 | 类别 | 要求 | |---|---| | 安全 | HMAC 常量时间校验、凭据隔离、Bot 自触发抑制、敏感字段脱敏 | | 一致性 | delivery/task/session/turn/comment 均有唯一键;状态变更可重放且幂等 | | 可恢复性 | 进程重启不丢队列、会话映射和回帖确认上下文 | | 可观测性 | 结构化日志 + 审计事件 + task/session/turn 关联 ID | | 性能 | Webhook 验签与入队不等待 Agent 完成;并发只受仓库锁约束 | | 数据保留 | 任务/会话/审计至少保留到管理员显式清理策略生效;工作区持久保留 | | 风险 | gitea-mcp 全写权限意味着 Bot 能修改代码和 Gitea 内容;通过 HMAC、暂停、 中止、审计和脏工作区保护降低风险,不声称实现细粒度授权 | ## 8. 验收标准与需求覆盖矩阵 每个验收项都应能在集成测试、协议 mock 或只读审计中独立验证。 | ID | 验收标准 | 覆盖需求/设计 | |---|---|---| | AC-01 | 合法 Issue 普通评论含 `@ccweb-bot` 创建任务;无 mention、非评论或 Bot 自评论不创建任务 | FR-01 | | AC-02 | HMAC 错误返回 401 且无业务写入;相同 delivery 重放不增加任务数 | FR-02 | | AC-03 | 首次合法触发自动登记仓库并 clone;再次触发只 fetch;脏工作区不被 reset/覆盖 | FR-03 | | AC-04 | 同仓库两个任务严格串行;两个仓库可并行;不额外施加全局并发上限 | FR-04 | | AC-05 | 同一 Issue/PR 多条评论复用一个 `threadId`,每条任务有唯一 `turnId` | FR-05 | | AC-06 | Codex App 运行时固定 `codexapp+yolo`;官方 gitea-mcp 以线程级 stdio 配置启动 | FR-05 | | AC-07 | 合法任务仅有一条“已收到”起始回执;成功正文由 MCP/REST 兜底承担,失败或控制终态最多一条结果回执,Bot 评论不会回触发 | FR-06 | | AC-08 | MCP 最终回帖能按 `taskId+turnId+resourceKey` 确认;缺失只补发一次,随后 REST 兜底 | FR-06 | | AC-09 | 重启后 queued 继续、waiting_user 保留、running 最多重试一次;终态不重跑 | FR-07 | | AC-10 | pause 阻止新领取;仓库 disable 拒绝新任务;cancel/abort 幂等且释放相应资源 | FR-08 | | AC-11 | 审计可按 task/session/repo 查询,包含状态迁移、delivery、turn、comment 和错误字段 | FR-08 | | AC-12 | PRD 与 ARCHITECTURE 的状态枚举、字段名、默认并发、回帖错误语义完全一致 | 文档一致性 | | AC-13 | 生产挂接包含 `server.js` 路由、`lib/gitea-workflow-*` 核心模块、管理页与离线回归;不新增第三方依赖 | 交付约束 | ## 9. 交付边界与后续演进 当前仓库已提供 MVP 实现:`POST /api/gitea/webhook`、持久任务/队列、工作区维护、 Codex App 线程级 gitea-mcp 注入、回帖确认/兜底、管理 API/UI 和离线回归。实现与 测试必须继续以本 PRD 的 FR/AC 编号作为审计索引。 后续可独立评估:多实例、细粒度授权、审批门、工作区隔离容器、任务预算、 SSE/前端实时镜像和更多 Forge 适配;这些不改变 MVP 的 `sessionKey`、幂等和回帖 确认约束。