## 让 Pull Request 关键活动通知团队

<!-- slot: 目标 -->
### 本章目标

重要 Pull Request 需要评审、代码更新、检查失败和合并等信息，但让所有人持续刷新代码托管页面既低效又容易漏看。本章使用 Gitea 场景说明：让已获授权的 Agent 管理一条最小范围订阅，由代码托管服务把符合条件的活动投递到指定 Loop 目标。

学完本章，你将能够：

- 用自然语言说清楚要关注哪个 Pull Request、哪些活动和哪个通知位置。
- 在创建前让 AI 盘点现有订阅，避免因为名称相似或状态不明操作错对象。
- 区分 Agent、App 授权人、代码托管服务和 Loop 接收目标各自的责任。
- 在暂停、恢复、移除或结果不明时，先读取当前状态，再由人决定下一步。
- 明确订阅只投递活动，不会自动启动 Workflow。

<!-- slot: 边界 -->
### 适用边界

- **覆盖**：当前已授权 Gitea Extension 提供的 Pull Request 订阅查询、创建或复用、暂停、恢复和移除。
- **不覆盖**：合并 Pull Request、发表评论、修改代码、创建 Issue，或调用当前授权目录中没有列出的动作。
- **假设**：管理员已经连接可用的 Gitea App；Agent 对该 App、目标仓库和 Loop 接收位置都有所需权限。

这项能力是持久订阅，不是 Workflow 节点，也不是自动触发器。Agent 负责管理订阅；真正的活动由代码托管服务成员投递到已授权目标，不是 Agent 先收到事件再转发。若团队收到活动后还要运行某项 Workflow，必须另行明确发起，并重新确认版本、输入和影响范围。

<!-- slot: 流程 -->
### 从一个 PR 链接到团队消息

假设团队只想关注一个重点 PR 的评审、代码更新、失败检查状态和合并，不想把所有评论都送进项目频道：

![Pull Request 订阅主路径：你提供 PR 链接、事件范围和 Loop 目标，AI 先查询现有订阅；候选不唯一时列出 watchId 等待选择，意图明确后经人工确认创建或复用，由代码托管服务把匹配活动投递到 Loop 目标，但不自动启动 Workflow](../../media/chapters/fig-18-01-pr-watch-and-delivery.svg){#fig-18-pr-watch-and-delivery width=96%}

“先查询”只能帮助发现候选，不能单独证明两个订阅请求完全相同。当前列表可返回订阅编号、PR、目标、状态和显式事件范围，但部分字段可能未提供，而且列表不会显示完整授权身份与所有创建选项；同一个 PR 和目标也可能存在多条事件范围不同的订阅。因此后续暂停、恢复或移除应优先使用结果卡里的精确订阅编号。

创建或复用成功后，订阅可能是启用、暂停或阻塞状态。复用一条暂停订阅不会自动把它恢复；AI 必须把当前状态展示给人，再决定是否恢复。

<!-- slot: 提示词 -->
### 复制这段订阅提示词

把方括号替换为真实信息。事件范围应显式列出，不要只写“有变化就通知”：

```text
请帮我为下面这个 Gitea Pull Request 建立最小范围通知。所有后台操作由你完成，但创建、恢复、暂停或移除前都要先等我明确确认。

Pull Request 页面链接：[链接]
通知发到哪里：[Loop 频道、Thread 或 Task]
只关注这些活动：[评审 / 代码更新 / 失败检查状态 / 合并 / 关闭 / 重新打开 / 评论]
谁确认创建或变更订阅：[负责人]
这条订阅何时可以暂停或移除：[业务条件]
通知关注人设置：[保持本次明确选择，不从候选列表猜测]

请按下面顺序处理：
1. 先核对当前授权目录中是否有 Gitea Pull Request 订阅能力；没有就停止，不要编造动作或要求我提供 token。
2. 用精确 PR 链接、明确的 Loop 目标和显式事件范围形成订阅意图。省略事件范围与显式写出一组事件不是同一个创建意图，不要互相当作重复请求。
3. 先做有界查询并处理所有返回页。列表只用于筛选候选；它不展示完整授权身份或通知关注人设置，所以不要仅凭列表断言某条订阅与本次请求完全相同。
4. 如果同一个 PR 与目标有多条候选，列出各自实际返回的订阅编号、状态和事件范围，缺失字段标为“未提供”，等我选择；不要只用 PR 与目标直接暂停、恢复或移除。
5. 创建前展示行动卡：PR、Loop 目标、事件范围、当前授权是否可用、将创建还是可能复用，以及不会自动启动 Workflow。等我确认后再提交。
6. 成功后返回订阅编号、PR、目标、事件范围、当前状态和可核验时间；回执未提供的字段标为“未提供”，不要从请求反推。当前公开回执不单独标示这次是新建还是复用，所以只写“已取得此订阅”，不要猜。若返回状态为暂停或阻塞，不要自动恢复。
7. “失败检查状态”只表示新的失败状态消息。新订阅在还没有从后续 PR 活动取得当前提交标识前，可能接不到第一条仅按提交标识到达的失败状态；过去已经发生的失败也不会补发。不要把暂时没有消息当成订阅失效。
8. 创建、暂停、恢复或移除没有明确响应时，先重新查询当前可见状态。暂停、恢复和移除不得盲目重放；无法核验时停止并让我判断。
9. 只有订阅当时处于启用状态、事件范围包含本次“合并”或“关闭”，并且对应终态消息已经成功投递时，订阅才会自然完成并退出当前列表。任一条件不满足，都先查询当前状态，再按既定业务条件请我确认是否主动移除；不要因为 PR 页面已经显示终态就假定订阅已结束。主动移除后也不要依赖列表寻找已删除记录。保存终态消息或移除回执作为审计证据。
10. 收到代码托管活动时，只投递到已确认的 Loop 目标。不要自动创建 Workflow Run；若需要运行流程，另行展示精确 Workflow、版本、输入和影响范围，再等我确认。

任何时候发现 PR、目标、事件范围或授权状态与行动卡不一致，都停止并请我重新确认。
```

这段提示词把“订阅什么”和“收到后做什么”分开：前者由本章管理，后者若涉及 Workflow，继续遵守第 17 章的独立确认边界。

### AI 和人分别做什么

| 角色 | 负责什么 | 不能代替谁 |
| --- | --- | --- |
| 你 | 提供 PR、事件范围和接收位置；确认创建及生命周期变更 | 不需要接触 Gitea 凭据 |
| AI 助手 | 查询候选、展示行动卡、调用已授权 Agent 完成确认后的管理动作 | 不能从列表猜完整创建意图，也不能自动启动 Workflow |
| 获授权 Agent | 在自身被授予的 App 权限内管理订阅 | 不是 Gitea App 的授权人，也不直接持有提供方 token |
| Gitea App 授权人或管理员 | 建立和维护 App Installation 与仓库连接 | 不替业务负责人决定每条 PR 通知范围 |
| 代码托管服务成员 | 把匹配活动投递到已授权 Loop 目标 | 不运行任意 Workflow |
| 团队接收者 | 根据消息评审、修复或决定下一步 | 不应把一条通知当成平台已经执行了业务动作 |

<!-- slot: 配置 -->
### 行动前确认五张信息卡

1. **PR 身份卡**：仓库、编号和页面链接必须唯一，不根据相似标题猜测。
2. **事件范围卡**：只选业务真正需要的活动。评论和分支更新较多时，应先从评审、失败检查状态和合并等高信号事件开始。
3. **接收位置卡**：明确是频道、Thread 还是 Task；接收位置既是投递目标，也是权限检查的一部分。
4. **授权关系卡**：Agent 的管理者与最初授权 Gitea App 的人是两种身份。AI 只能说明当前授权是否可用，不能把 Agent 的机器所有者写成 App 授权人。
5. **生命周期卡**：显示精确订阅编号和当前状态，说明这次要创建、保持、暂停、恢复还是移除。

当前默认事件范围包含关闭、评论、合并、重新打开、评审、失败状态和 PR 代码更新；目标分支推进提醒需要显式选择，因为它可能产生较多消息。为了让创建意图和以后审计一致，本章仍要求显式列出业务需要的事件，不依赖默认集合。

“失败检查状态”不会发送成功、等待或运行中状态；它只保留失败信号。验证时应在订阅已取得当前 PR 提交标识后制造一次新的失败，不能用“成功检查没有消息”判断订阅坏了。

<!-- slot: 审批 -->
### 两层授权不要混在一起

管理员连接 Gitea App，决定 Workspace 能否访问相应仓库；业务负责人决定某个 PR 的哪些活动可以投递到哪个 Loop 目标。这两层都成立，订阅才能工作。

当 Agent 管理订阅时，平台会核对 Agent 的当前 App 授权和目标权限。Agent 的机器所有者只是 Agent 管理关系的一部分，不等于最初批准 App Installation 的授权人。结果卡只应写“当前授权可用”或“需要管理员处理”，不能猜测具体授权者。

创建通知会持续产生外部活动消息，暂停会造成一段时间静默，恢复会重新接收，移除会结束订阅。四类动作都应先展示精确订阅和业务影响；AI 不得因为团队暂时没有互动就自行暂停或移除。

### 订阅状态怎样变化

![Pull Request 订阅生命周期：启用订阅经人工确认可暂停，暂停订阅经确认可恢复；状态变更响应不明时先查询再决定；只有订阅启用中、事件范围包含本次合并或关闭且终态消息投递成功，订阅才自然完成并退出当前列表；条件不全时先查询状态，再按业务条件确认是否主动移除](../../media/chapters/fig-18-02-pr-watch-lifecycle.svg){#fig-18-pr-watch-lifecycle width=96%}

暂停、恢复和移除是状态变更，不是可以随意重复的“设置”。第一次响应丢失时，真正状态可能已经变化；先查询、再决定，可以避免把第二次请求作用到错误状态。

“PR 已经合并或关闭”不等于“订阅已经自然完成”。自然完成还要求订阅当时处于启用状态、事件范围包含对应终态，并且终态消息成功投递。若订阅处于暂停或阻塞状态、没有订阅本次终态，或终态投递尚未成功，当前列表里仍可能保留它；此时应先查询状态，再按团队预先约定的业务条件确认是否主动移除。

<!-- slot: 异常 -->
### 常见异常与安全处置

| 你看到的现象 | 先判断什么 | 安全的下一步 |
| --- | --- | --- |
| 当前没有 Gitea 订阅能力 | Agent 未获授权，或授权目录尚未刷新 | 请管理员恢复连接或授权；不要索要 token，也不要猜动作名 |
| 查到多条相同 PR 与目标 | 事件范围或其他创建意图不同 | 展示订阅编号、事件和状态，由人选择精确对象 |
| 已取得订阅但仍没消息 | 订阅可能暂停、阻塞，或没有匹配的新活动 | 先看当前状态和事件范围，不自动恢复或新建 |
| 新建后第一条失败状态没有到达 | 订阅可能尚未从后续 PR 活动取得当前提交标识 | 先让 PR 产生一次可识别的后续活动，再制造新的失败状态验证；不补猜历史消息 |
| 成功检查没有消息 | 失败状态过滤本来就忽略成功、等待和运行中 | 用一次新的失败检查验证，不扩大成所有状态 |
| 暂停、恢复或移除没有明确响应 | 网络中断不等于动作失败 | 重新查询当前可见状态；无法核验时停止，不盲目重放 |
| PR 合并或关闭后列表里找不到订阅 | 订阅可能已在“启用中 + 事件范围含本次终态 + 终态消息投递成功”后自然完成 | 先用终态投递消息和既有订阅编号核验；不要新建同意图只为找旧记录 |
| PR 已合并或关闭，但订阅仍在列表里 | 订阅可能暂停、阻塞、未包含本次终态，或终态消息尚未成功投递 | 查询实际状态和事件范围；按既定业务条件请人确认是否主动移除，不要假定平台会补做自然完成 |
| 订阅被阻塞 | App 连接、目标权限或投递出现问题 | 保留订阅编号，请管理员恢复条件；不要把凭据放进对话 |
| 收到消息但 Workflow 没运行 | 订阅只负责投递活动 | 如果确实要运行，另行发起并确认 Workflow 版本、输入和影响 |

限流或服务不可用时，AI 可以在不产生重复副作用的只读查询上稍后重试。对创建或状态变更，先保存完整意图和已有回执，再查询当前状态；不能把“等了一会儿”当成可以原样重复的理由。

### AI 应该返回哪些可见结果

创建或复用后，结果卡至少包含：

| 项目 | 应显示什么 |
| --- | --- |
| PR 身份 | 仓库、编号和页面链接 |
| 订阅身份 | 精确订阅编号 |
| Loop 目标 | 实际投递到的频道、Thread 或 Task |
| 事件范围 | 显式列出的事件，不用“所有变化”代替 |
| 当前状态 | 启用、暂停或阻塞；只有满足自然完成三项条件才记为完成，主动移除另存移除回执 |
| 动作结果 | 创建请求只写“已取得此订阅”，不猜新建或复用；暂停、恢复和移除按精确回执显示 |
| 授权说明 | 当前授权可用或需管理员处理；不猜具体授权人 |
| 审计信息 | 本次确认人、动作时间和可核验回执 |
| Workflow 边界 | 明确写“本订阅不会自动启动 Workflow” |

列表结果不能显示完整授权身份和全部创建选项，所以它是一份候选清单，不是完整审计证明。真正的创建意图还要结合行动卡、确认记录和创建回执保存。

<!-- slot: 外部系统 -->
### 外部系统

本章唯一的外部系统是 Gitea。提供方凭据由 App 连接保管，不进入提示词、Workflow 输入或结果卡。Agent 只调用当前授权目录中的订阅管理能力；代码托管服务成员按持久订阅把活动写入 Loop。

外部系统可能重复投递或延迟活动，平台会用提供方事件身份做重复保护，但业务读者仍应以消息中的 PR 链接、活动类型和时间判断上下文。订阅消息不是审批结果，也不是合并、评论或运行 Workflow 的证明。

### 业务收益怎样衡量

选择一到三个真实重点 PR，连续记录至少四周或累计二十条匹配活动：

- 从失败检查、关键评审或合并发生到团队看到消息的时间。
- 不在明确事件范围内的噪声消息数量，目标逐步下降。
- 同一个提供方活动产生重复可见消息的次数，目标为 0。
- 因订阅暂停、阻塞或授权失效造成的漏通知，是否能在一个工作日内被发现并定位到订阅编号。
- 收到消息后仍需人工询问“这是哪个 PR、发生了什么”的次数，是否持续下降。

样本不足时只记录事实，不宣称重复率已经为 0，也不判断趋势。订阅的价值是更快找到需要人处理的高信号变化，而不是把所有代码托管活动搬进聊天。

<!-- slot: 验收 -->
### 如何判断这套方法可以投入日常使用

- [ ] 能唯一指出 PR、Loop 目标和显式事件范围，不用“所有变化”代替。
- [ ] 创建前先查询候选；候选不唯一时用订阅编号让人选择，不根据 PR 与目标猜。
- [ ] 创建、暂停、恢复和移除分别等待明确确认，结果不明时先查状态。
- [ ] 能区分 Agent 管理者、App 授权人、代码托管服务成员和团队接收者。
- [ ] 明白失败状态只投递新的失败，并能按当前提交标识设计一次有效验证。
- [ ] 能说明只有“订阅启用中 + 事件范围包含本次合并或关闭 + 终态消息投递成功”才自然完成；条件不全时先查状态，再按业务条件确认是否主动移除。
- [ ] 自然完成或主动移除后，用终态消息或移除回执审计，不要求当前列表找回旧记录。
- [ ] 每张结果卡都明确订阅不会自动启动 Workflow。

<!-- slot: 媒体槽位 -->
### 媒体槽位

<!-- media: fig-18-01-pr-watch-and-delivery.svg -->
图 18-1 PR 订阅主路径：查询候选、人工确认、创建或复用，以及代码托管服务到 Loop 的投递。

<!-- media: fig-18-02-pr-watch-lifecycle.svg -->
图 18-2 订阅生命周期：启用、暂停、恢复、自然完成的三项前置条件，以及条件不全或结果不明时先查询、再确认是否主动移除。

### 小结

让 PR 关键活动通知团队的关键，不是“打开一个开关”，而是把 PR、事件范围、接收位置、授权关系和订阅编号说清楚。先查询候选，再确认创建；状态变更结果不明时先读后写；PR 终结后也要核对启用状态、终态事件范围和投递结果，不能把提供方终态直接当成订阅终态。代码托管服务负责投递，订阅本身不启动 Workflow。这样既能减少刷新页面的负担，也不会把一条通知误解成已经完成了业务动作。

下一章会从这条真实集成继续扩展：面对 API、Webhook、文件交换或人工导入等其他系统时，怎样选择合适的连接方式，并明确哪些能力已经存在、哪些仍需实施。
