Files
cfdaily b07e311921
CI / lint (pull_request) Successful in 6s
CI / test (pull_request) Successful in 8s
CI / notify-on-failure (pull_request) Successful in 0s
chore(docs): 归档 §22 §23 至 archive-3.0,§13 追加 §7.6
- 22-cd-production.md → archive-3.0/(部署成功通知草案)
- 23-toolchain-pr-lifecycle.md → archive-3.0/(PR 全生命周期,已由 §13 §16 覆盖)
- §13 §7 新增 §7.6 部署成功通知(草案引用)
2026-06-13 08:51:35 +08:00

9.8 KiB
Raw Permalink Blame History

§23 — 工具链事件中枢补全:PR 全生命周期通知

状态:草案,待评审 作者:庞统 日期:2026-06-11 框架:基于 §20 Task Type Architecture + §13 工具链设计

背景

问题

工具链事件中枢(toolchain_routes.py)当前只覆盖了 PR 生命周期中约一半的交互节点。review 驳回后 PR 作者修改代码,没有机制通知 reviewer 重新 review——流程在这里断链。

当前覆盖

事件节点 handler 模板 状态
PR 创建 → 通知 reviewer _handle_pull_request (opened) review_request.md
Review 通过 → 通知 PR 作者 _handle_pull_request_review (APPROVED) review_result.md
Review 驳回 → 通知 PR 作者 _handle_pull_request_review (REQUEST_CHANGES) review_result.md
Issue 指派 → 通知被指派人 _handle_issues (assigned) issue_assigned.md
CI 失败评论 → 通知 _handle_issue_comment ([CI]) ci_failure.md
部署失败 Issue → 通知 _handle_issues (opened+"部署失败") deploy_failure.md

缺失节点

# 事件节点 Gitea 事件 优先级 理由
E1 PR 更新(push 新 commit)→ 通知 reviewer pull_request.synchronize review 驳回→修改→重 review 的关键闭环
E2 PR 合并通知 已删除 和 §22 CD 成功通知重叠,已删
E2 PR 合并 → 通知 PR 作者 + 自动部署 pull_request (closed+merged) PR #38 恢复:CD 通知语义不同(部署状态 vs 合并信息),文档 PR 无 CD 流程仍需通知。PR #43:含自动部署(git pull + pm2 restart
E3 Review 评论(COMMENTED)→ 通知 PR 作者 pull_request_review (COMMENTED) reviewer 讨论提问,作者应知道
E4 PR 上普通评论 → 通知相关人 issue_comment (on PR) 非关键路径

方案

框架对齐

按 §20 Task Type Architecture,新增事件处理遵循:

  1. _EVENT_HANDLERS 映射 → 路由到对应 handler 函数
  2. handler 提取变量 → render_template() 渲染模板
  3. _TEMPLATE_MAP 注册模板名 → templates/toolchain/ 下新建模板文件
  4. 通知目标通过 Gitea username → to_agent_id() 映射

新增 Handler 1_handle_pull_request_synchronize

触发pull_request 事件 + action=synchronizePR 分支有新 push

通知对象PR 的 reviewer(从 PR 的 requested_reviewers 或最近一次 non-COMMENTED review 的提交者)

实现

修改 _handle_pull_request 的 action 过滤,从只处理 opened 扩展为同时处理 synchronize

async def _handle_pull_request(payload: Dict[str, Any]) -> None:
    action = payload.get("action", "")
    if action == "opened":
        await _handle_pr_opened(payload)
    elif action == "synchronize":
        await _handle_pr_synchronize(payload)
    # 其他 action 忽略

新增 _handle_pr_synchronize

  1. 从 payload 取 PR 信息(number、title、author、head sha
  2. 查询最近一次 reviewGitea API GET /repos/{owner}/{repo}/pulls/{number}/reviews)取 reviewer
  3. 如果没有 review 记录(_fetch_latest_reviewer() 返回 None),fallback 到默认 reviewer simayi-challenger,而非跳过通知(PR #38 改动:确保无 review 历史时也能通知默认审查者)
  4. 渲染 review_updated.md 模板,发送 Mail 给 reviewer

关键设计决策

  • 不用 requested_reviewers(可能为空),用最近 review 的提交者
  • 无 review 历史时 fallback 到默认 reviewer simayi-challengerPR #38:避免 opened + synchronize 间隔较短时 reviewer 未收到任何通知)
  • Mail from 用 system

Handler 2_handle_pr_closed(PR 合并通知)— PR #38 恢复

触发pull_request 事件 + action=closed + merged=true

通知对象PR 作者

实现

修改 _handle_pull_request 的 action 分发,新增 closed 分支:

async def _handle_pull_request(payload: Dict[str, Any]) -> None:
    action = payload.get("action", "")
    if action == "opened":
        await _handle_pr_opened(payload)
    elif action == "synchronize":
        await _handle_pr_synchronize(payload)
    elif action == "closed" and payload.get("pull_request", {}).get("merged"):
        await _handle_pr_closed(payload)
    # 其他 action 忽略

新增 _handle_pr_closed

  1. 从 payload 取 PR 信息(number、title、merged_by
  2. merged_by 优先从 payload["pull_request"]["merged_by"] 取,若为空则 fallback 到 payload["sender"]PR #38:兼容不同 Gitea 版本和 merge 方式)
  3. 渲染 review_merged.md 模板,发送 Mail 给 PR 作者

恢复说明:此前因与 §22 CD 成功通知重叠而删除。但实际场景中 CD 通知发的是部署状态,PR 作者更关心的是"谁帮我 merge 了"这个信息,两者语义不同。且 CD 流程不一定每次都触发(如文档 PR),merge 通知仍需独立存在。(PR #38 恢复)

新增 Handler 3review COMMENTED 处理

触发pull_request_review 事件 + state=COMMENTED

通知对象PR 作者(不是 reviewer

实现

修改现有 _handle_pull_request_review,当前逻辑是"非 COMMENTED 才通知",改为 COMMENTED 也通知,但用不同模板:

    # 现有逻辑:非 COMMENTED 通知 PR 作者
    if state in ("APPROVED", "REQUEST_CHANGES"):
        template_name = "review_result"
    elif state == "COMMENTED":
        template_name = "review_comment"
    else:
        return  # PENDING 等忽略

新增模板

模板文件 变量 说明
review_updated.md repo, pr_number, pr_title, pr_author, branch, new_sha, reviewer PR 有新 commit,请重新 review
review_merged.md repo, pr_number, pr_title, pr_author, merged_by PR 已合并,通知作者(PR #38 恢复)
review_comment.md repo, pr_number, pr_title, reviewer, comment_body reviewer 提交了评论

_EVENT_HANDLERS 无需改动

synchronizeclosed 都是 pull_request 事件的 action 子类型,已映射到 _handle_pull_request。COMMENTED 是 pull_request_review 的 state 子类型,已映射到 _handle_pull_request_review

所以 _EVENT_HANDLERS 不需要修改,只需修改 handler 内部的 action/state 分发逻辑。

PR 合并后自动部署(PR #43

触发_handle_pr_closed 合并事件处理完成后

逻辑

  1. 读取 config/deploy-targets.yaml,查找 repo 对应的部署目标
  2. 不在配置中 → 跳过(未来新项目加一条配置即可)
  3. git pull origin main(开发目录)
  4. rsync 同步到安装目录(排除项由配置指定)
  5. 判断是否需要执行 post_deploy:文件路径包含 src/templates/frontend/*.py 后缀
  6. docs/ 变更 → 只 pull + rsync,不执行 post_deploy
  7. 部署失败复用 deploy_failure.md 模板通知 jiangwei-infra + pangtong-fujunshi

配置文件config/deploy-targets.yaml(集中管理所有仓库的部署目标)

targets:
  sanguo/sanguo_moziplus_v2:
    dev_dir: ~/.openclaw/sanguo_projects/sanguo_moziplus_v2
    install_dir: ~/.sanguo_projects/sanguo_moziplus_v2
    pm2_name: sanguo-moziplus-v2
    rsync_exclude: [.git/, node_modules/, __pycache__/, data/]
    health_check: http://localhost:8083/health
    post_deploy: [pm2 restart sanguo-moziplus-v2]

设计决策

  • 集中式 YAML 配置(姜维建议):部署是平台级能力,非仓库级。一个文件管所有仓库,新增项目零代码改动
  • YAML 而非 JSON:支持注释,方便临时禁用某个仓库或排除项
  • post_deploy 列表:支持任意 shell 命令,不只是 pm2 restart。未来可扩展(如 pip install -e .
  • health_check 字段:预留,后续可用于部署后健康检查
  • 失败通知复用:CD 失败和 CI 失败用同一套通知机制(deploy_failure.md 模板 + _send_mail
  • git pull 在开发目录(有 .git),rsync 到安装目录
  • 全异步调用(asyncio.create_subprocess_exec

不做的事

理由
E4 PR 上普通评论通知 低优,非关键路径,后续按需加
Issue 关闭通知 低优,关怀性质
reviewer 从 requested_reviewers 不可靠(可能为空),用最近 review 记录更稳定

改动范围

文件 改动
src/api/toolchain_routes.py 修改 _handle_pull_request(扩展 action 分发 + closed 分支)+ 新增 _handle_pr_synchronize + _handle_pr_closed + 修改 _handle_pull_request_review(支持 COMMENTED
templates/toolchain/review_updated.md 新增
templates/toolchain/review_merged.md 新增(PR #38 恢复)
templates/toolchain/review_comment.md 新增
src/daemon/toolchain_templates.py _TEMPLATE_MAP 新增 3 个映射
docs/design/23-toolchain-pr-lifecycle.md 本文档

验证计划

sanguo/moziplus-v2 测试仓库上 E2E 验证:

  1. synchronize:创建 PR → review 驳回 → push 新 commit → 验证 reviewer 收到"请重新 review" Mail
  2. synchronize fallbackPR #38):创建 PR → push commit(无 review 历史)→ 验证默认 reviewer (simayi-challenger) 收到通知
  3. merge 通知PR #38 恢复):PR merge → 验证 PR 作者收到合并通知 Mail
  4. COMMENTED:review 提交纯评论 → 验证 PR 作者收到通知

风险评估

  • 风险等级:低。新增事件处理,不修改现有 handler 逻辑
  • 幂等性:复用现有 _is_duplicate 机制
  • 性能synchronize handler 有一次 Gitea API 调用(查 review 历史),频率低(只在 push 后触发)