feat: support MCP elicitation and rebuild release

This commit is contained in:
shiyue
2026-08-24 17:39:41 +08:00
parent dd233a40e8
commit bd20a79d4b
69 changed files with 8978 additions and 13 deletions

View File

@@ -0,0 +1,147 @@
# Gitea Webhook Agent 部署与运维设计
> 本文是 [PRD.md](./PRD.md) 与 [ARCHITECTURE.md](./ARCHITECTURE.md) 的运行手册。
> 它定义部署、凭据、恢复和故障处置边界Gitea 仍是唯一主交互入口cc-web 页面
> 只承担配置、镜像和运维控制。
## 1. 运行基线
MVP 采用单 Gitea 实例(`instanceId=default`)和专用 `ccweb-bot`。同仓库永远
串行,跨仓库允许并行,不设置全局并发上限。生产环境至少需要:
| 项目 | 基线 |
|---|---|
| Node/Bun | 使用项目锁定的运行时和已验收的单文件发布包;不要混用系统 Node |
| 进程 | 一个 cc-web worker外部 Codex App Server 按会话复用 |
| 持久目录 | `WORKFLOW_DATA_ROOT`(任务/审计/租约)与 `WORKSPACE_ROOT`Git 工作区)分离 |
| 网络 | Gitea Webhook 入站、Gitea REST/MCP 出站、Codex App Server 本地 stdio/JSON-RPC |
| 时间 | 主机启用 NTP持久化时间全部使用 UTC ISO-8601 |
| 访问 | 反向代理终止 TLSWebhook 路径只允许 POST 和受限请求体大小 |
建议为 worker、工作区和日志使用独立系统用户。`WORKFLOW_DATA_ROOT` 仅 worker
可读写,`WORKSPACE_ROOT` 不允许 Web 静态服务暴露,备份目录使用不同权限主体。
## 2. 配置与凭据
配置按“非敏感文件 → Secret 管理 → 线程级注入”分层。示例(值均为占位符):
```ini
CC_WEB_GITEA_HOST=https://gitea.example
CC_WEB_GITEA_INSTANCE_ID=default
CC_WEB_GITEA_BOT_LOGIN=ccweb-bot
GITEA_WEBHOOK_SECRET_FILE=/etc/ccweb/secrets/gitea-webhook
GITEA_BOT_TOKEN_FILE=/etc/ccweb/secrets/gitea-bot-token
CC_WEB_GITEA_WORKFLOW_STATE=/var/lib/ccweb/gitea-workflow/state.json
CC_WEB_GITEA_WORKSPACE_ROOT=/var/lib/ccweb/workspaces
```
cc-web 管理页对应接口为:
- `GET /api/gitea-workflow/config`返回地址、Bot 用户名、工作区和凭据是否已配置;不会返回 Secret/Token。
- `PUT /api/gitea-workflow/config`:保存上述配置,敏感字段写入受保护的运行配置;保存后立即更新当前进程的 Webhook、Git 和 gitea-mcp 线程配置,不要求重启 cc-web。
- `GET /api/gitea-workflow/overview``/tasks``/audit`:状态镜像。
- `POST /api/gitea-workflow/control/pause|resume`、仓库 `enable|disable`、任务 `cancel|abort`:需要 cc-web 登录 Bearer Token服务端自动记录操作来源和请求幂等 ID页面不要求用户填写审计字段。
- 内部部署可以不配置 Webhook Secret配置后入口启用 HMAC-SHA256 验签。Bot Token
只在 gitea-mcp/REST 出站边界使用,不能把 Bot Token 当作 Webhook Secret。
- Secret 文件权限为 `0600`、属主为 worker日志、审计、评论正文和 Git remote
URL 禁止写入 Secret。审计只记录 `secretRef/tokenRef` 和不可逆指纹。
- `thread/start.config.mcp_servers.gitea.env` 使用线程级 secret 引用或运行时
注入值;不能把某个来源会话 ID、Bot Token 或 Gitea Secret 放在长驻
app-server 的进程级全局环境中。
- `gitea-mcp` 随 cc-web 源码/发布目录放在 `bin/gitea-mcp`,不需要为每个服务单独
安装。只有替换版本时才使用 `CC_WEB_GITEA_MCP_COMMAND` 指向本机绝对路径;可选的
`CC_WEB_GITEA_MCP_STARTUP_TIMEOUT_MS` 控制 initialize 预检超时(默认 8000ms
上限 30000ms。cc-web 发布流程不从网络自动下载或安装外部二进制。
- 轮换 Secret 时先配置新值并验证签名,再撤销旧值;未配置 Secret 的内部入口应
由反向代理/网络白名单限制来源。Bot Token 更新后立即作用于后续 MCP 线程。
## 3. Webhook 与反向代理
Gitea 只向反向代理暴露的 HTTPS 路径发送 Webhook。代理应
1. 严格保留原始请求 body 和 `X-Gitea-Signature``X-Gitea-Delivery`
`X-Gitea-Event` 头,不做 JSON 重排后再交给验签层。
2. 限制方法为 POST、请求体大小建议 1 MiB和连接超时超限返回 413。
3. 只将来自 Gitea 网段的请求转发到 worker不要在代理层伪造签名或 delivery。
4. 配置了 Secret 时worker 验签失败返回 401解析失败返回 400首次入队返回
202重复 delivery/忽略事件返回 200。未配置 Secret 的内部入口仍需由代理
限制来源;持久化不可用时返回 503。
5. 代理访问日志脱敏 `Authorization`、签名值和正文;保留 request ID 以便与
`deliveryKey` 关联。
验签必须针对 raw body 计算 HMAC-SHA256并使用常量时间比较。解析后的 mention
和仓库字段不能替代 raw body 验签;缺少 delivery header 时只能使用可靠 payload
ID否则拒绝入队。
## 4. 启动、健康检查与发布
启动顺序固定为:
1. 检查配置文件、Secret 文件权限、数据目录可写性和工作区根目录是否在允许
根下;禁止自动 `reset --hard``clean`
2. 打开持久 Store执行租约过期扫描和恢复扫描`queued` 重新入队,
`running/preparing/aborting` 按架构规则转为带 `interrupted_by_restart`
`retry_wait`,终态不重跑。
3. 启动 Scheduler 和受控的 Codex App Server完成 `initialize` 后发送
`initialized`,再 best-effort 探测 goals 与 collaboration mode。
4. 仅在健康检查通过后把 Webhook 代理切入当前 worker。
健康检查至少包含Store 读写探针、租约扫描完成、Gitea `/api/v1/version`
等价只读探针、Codex App Server initialize smoke以及线程级 gitea-mcp
stdio `initialize` 预检。健康检查不得创建评论、修改仓库或打印凭据。
发布采用“先旁路验证、再切流”:在 staging 用回归 fixtures 和真实 Gitea 测试
仓库验证 HMAC、入队、MCP 配置与回帖确认;生产切换前确认旧 worker 已停止领取
新任务且租约已释放。回滚只切换到上一份已验收发布包和数据快照,不删除当前
工作区或审计。
## 5. 日常运维控制
| 操作 | 影响 | 约束 |
|---|---|---|
| 全局 pause | 停止领取新任务,已运行任务继续 | 页面按钮直接执行,服务端记录操作 |
| resume | 恢复调度 | 先检查工作区和 Gitea 连通性 |
| repo disable | 拒绝该仓库新触发 | 不强杀运行中任务,保留历史 |
| cancel queued | 取消未启动任务 | 幂等;不得取消已领取任务 |
| abort running | 向 Codex App 请求取消 | 等待确认或超时后释放租约并审计 |
| replay delivery | 仅用于核对/恢复 | 先查 `deliveryKey`,禁止绕过去重直接执行 |
所有控制操作必须通过受保护的运维 API服务端生成操作记录和幂等 ID。API 不接受
任意 Git 命令、模型切换或 MCP 工具参数。
## 6. 监控与告警
结构化日志只输出关联 ID`deliveryKey、taskId、repoKey、sessionKey、threadId、
turnId、leaseId、errorCode`。建议监控Webhook 401/400/503 比例、队列年龄、锁等待、
运行任务数量、`retry_wait` 数量、`blocked_workspace` 数量、MCP 握手失败、回帖确认
缺失和 REST 兜底次数。
以下情况应告警并暂停自动扩容:连续 HMAC 失败、同一 delivery payload hash
变化、租约频繁过期、回帖兜底连续失败、工作区出现未知 owner 或路径越界、
Bot Token/Secret 可能泄露。告警正文不得包含 Secret、完整评论正文或源码。
## 7. 备份、恢复与数据保留
- 备份 Store 的任务、会话、turn、评论索引、控制面和 append-only 审计;
`webhook_deliveries` 至少保留覆盖 Gitea 重试窗口的记录。
- 工作区是可重建缓存,备份前先记录 `repoKey、HEAD、dirty 状态`;不要把
未提交用户修改静默覆盖或当成可恢复快照。
- 恢复顺序:停止领取 → 恢复 Store → 校验版本/唯一键 → 执行租约恢复扫描 →
只读检查工作区 → 启动 Scheduler → 逐步 resume。恢复后优先核对
`deliveryKey → taskId → turnId → commentId` 链路,避免重复回帖。
- 清理策略必须由管理员显式启用并保留审计;不得因磁盘告警直接删除运行中
任务、未确认评论或工作区。
## 8. 故障处置速查
| 症状 | 先查 | 安全动作 |
|---|---|---|
| Webhook 全部 401 | Secret 引用、raw body、代理头 | 暂停切流,验证单个 fixture不打印签名 |
| 任务堆积 | pause、锁、并发槽位、租约 | 不手工改状态;修复后让恢复扫描接管 |
| 工作区 blocked | `git status`、owner、锁文件 | 保留现场,管理员审查后再解除;禁止 reset/clean |
| App/MCP 不可达 | initialize、stdio stderr、hostRef、`gitea-mcp` 可执行文件 | 命令缺失、进程启动失败、握手失败或超时均立即进入 `failed` 并发送一条 `giteabot:` 失败回执;修复后重新触发 |
| MCP 成功但无评论 | resource/task/turn 标记、Gitea 查询 | 只允许一次 reply_retry随后 REST 兜底 |
| 重启后重复执行 | stateVersion、leaseId、终态记录 | 先 pause修复 Store/租约,再恢复;禁止批量重放 |
每次事故结束后导出关联审计、错误码、重试次数和最终评论 ID形成可复盘记录。