docs(purchase): 更新 Agent 重试契约 (#217)

This commit is contained in:
QiuSW
2026-09-05 09:03:07 +08:00
parent bd386cfae4
commit 96cc2ab700
2 changed files with 31 additions and 35 deletions
+10 -12
View File
@@ -2,8 +2,8 @@
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Business-Rules-and-Glossary
wiki_url: https://git.ilapage.cn/OPC/goauto/wiki/Business-Rules-and-Glossary.-
wiki_revision: 069248c1466d54273900a4c87d1285436f7d50f9
synchronized_at: 2026-09-04T10:57:41Z
wiki_revision: bd2748f2b58daee92eaa1e5ed16ce61fa18f728f
synchronized_at: 2026-09-05T01:00:01Z
<!-- gitea-wiki-mirror:end -->
# 业务规则与术语
@@ -256,16 +256,14 @@ synchronized_at: 2026-09-04T10:57:41Z
## Agent 受控重试采购
- 当前设备只可就地重跑自身最近 30 天内、服务端标记 `retryable=true` 的正式采购失败任务;列表和详情都只能发起单任务重试,不支持多选、批量或自动重试。
- 普通“重试采购”复用原 `purchase_task.id`,不新建任务,不改变商品、虾皮/SYB 身份、目标规格、已映射规格、价格保护、地址后缀与其他业务快照。重试只把任务恢复为 `pending`,清理错误、租约和运行守卫。
- 重试时刷新服务端当前采购规则及其类型、schema 和能力要求。#127 尚未实施前,当前规则仍为服务端内置规则;#127 完成后才切换为数据库单例设置,不能在本工单提前引入第二事实源。
- 新规则下发前必须重新校验原设备在线、空闲且具备全部能力。规则不可用或能力不匹配时拒绝重试,原任务保持失败状态。
- 已出现 `order_submit_started` 证据,或存在不可逆时间、订单提交请求、PDD 订单号、下单时间的任务一律拒绝就地重试,并提示走既有“授权重新采购”流程,防止重复下单。
- 每次成功受理就地重试都会为同一任务预建一个新的 `pending` attempt,保存本次规则哈希;正常 Start 复用并转为 `running`。因此尝试次数、每次规则哈希和失败原因均可追溯,且不限制人工重试次数。
- `requestId` 按“任务 + 请求”幂等;相同请求重放不重复修改任务或增加 attempt。
- Agent 详情显示已尝试次数与上次失败原因。确认界面必须说明同一任务使用最新规则重跑、可能创建待付款订单且系统不会支付。
- “继续采购”与普通“重试采购”语义不同:替代商品匹配完成后的“继续采购”继续调用既有 `AgentRetry → BatchRetry → Create`,保留旧任务并按当前替代商品档案创建新任务;Admin 批量重试同样继续创建新任务。二者均不得改成就地更新旧任务。
- 取消订单、修改既有订单和支付仍禁止;真机重跑可能进入创建待付款订单流程,执行前必须再次取得人工授权。
- 当前设备只可重试自身最近 30 天内、服务端标记 `retryable=true` 的正式采购失败任务;列表和详情都只能发起单任务重试,不支持多选、批量或自动重试。
- 普通“重试采购”调用既有 `AgentRetry → BatchRetry → Create`:原失败任务及其商品、目标规格、执行规格、价格和执行记录保持不可变;服务端根据当前 SYB、虾皮/PDD 档案、当前采购规则和当前设备创建不同 `purchase_task.id` 的新任务。
- 新 SYB 采购任务继续遵循 #215 的强制当次规格探测,首趟不得直接使用历史任务的规格决策;当前档案、规则、价格、设备或能力门禁不通过时拒绝创建,旧任务保持失败状态。
- 已出现 `order_submit_started` 证据,或存在不可逆时间、订单提交请求、PDD 订单号、下单时间的任务一律拒绝重试,并提示走既有“授权重新采购”流程,防止重复下单。
- `requestId` 按“来源任务 + 请求”幂等;相同请求重放返回同一新任务,不重复创建。
- Android 只在服务端 `retryable=true` 且状态为 `failed` 时显示普通“重试采购”。确认和成功反馈必须说明旧任务保留、新任务读取当前档案和规则、可能创建待付款订单且系统不会支付。
- 历史兼容的就地 `/reset` 服务端入口不得把“目标规格存在但对应执行规格为空”的任务恢复到正式采购阶段;此类异常快照必须拒绝,并提示创建新任务。Android 普通重试不再调用该入口。
- 替代商品匹配完成后的“继续采购”和 Admin 批量重试继续使用同一新任务语义;取消订单、修改既有订单和支付仍禁止。真机重试可能进入创建待付款订单流程,执行前必须再次取得人工授权。
## Agent 状态页手动检查任务
+21 -23
View File
@@ -2,8 +2,8 @@
generated: true (请先修改 Gitea Wiki,禁止直接编辑本文件)
wiki_page: Android-Agent-API-Contract
wiki_url: https://git.ilapage.cn/OPC/goauto/wiki/Android-Agent-API-Contract.-
wiki_revision: a022259ba39db7588eaa3b6579c799e2eb6a6f9e
synchronized_at: 2026-09-04T10:58:12Z
wiki_revision: 644b864833730130cf4a2098a01611da0c0ac45d
synchronized_at: 2026-09-05T01:01:14Z
<!-- gitea-wiki-mirror:end -->
# MVP 共享 API 契约
@@ -539,7 +539,7 @@ Admin 列表与详情由 #35 实现;#67 增加 `shopeeOrderNoSnapshot` 的列
结果提交至少关联 `taskId`、`taskAttemptId`、`deviceId`、规则快照哈希和结构化结果。相同 attempt 的相同结果重复提交返回同一事实;不同内容拒绝覆盖。每个新 SYB 采购任务的第一趟只读遍历当次 PDD 规格面板并提交颜色、尺码原始候选,随后释放设备与已知账号租约并进入 `spec_probe_pending`;服务端只以任务冻结的 SYB 目标和当次候选先做繁简、空白/全半角/大小写及公斤/斤的唯一确定性匹配,仍无唯一结果才调用 AI。AI 的颜色和尺码必须逐字属于当次对应候选,否则按无匹配失败。第二趟只会收到服务端固化的精确 PDD 原始标签;Agent 只在已打开的规格面板内做有限纵向滑动,每次重新读取节点并按完整规范化文字精确点击,连续没有新证据或达到上限即停止。尺码的任务目标与页面值在选择边界使用同一安全尾价规范化;不改写任务快照,规范化为空、仍含货币符号或多个原始候选折叠为同一值时安全失败。
任务 payload 的必传布尔字段 `specResolutionAllowed` 是 Android 是否可以提交规格探测的唯一资格事实。新建 `taskType=syb_order` 任务必须由声明 `purchase.spec-probe.v1` 的规则创建,初始 `SpecDecisionRequestID` 为空且 `specSource=unresolved`,首趟返回 `true`;当次决策固化后返回 `false`。`stock`、`direct_select`、已固化规格决策、能力缺失及其他组合均返回 `false`。普通就地重试保留 `SpecDecisionRequestID`、目标规格、映射规格和规格决策快照,不能恢复探测资格;第二趟选择失败必须提交真实失败,不能重新探测。Android 不得根据映射是否非空、错误文字或本地判断扩大资格。
任务 payload 的必传布尔字段 `specResolutionAllowed` 是 Android 是否可以提交规格探测的唯一资格事实。新建 `taskType=syb_order` 任务必须由声明 `purchase.spec-probe.v1` 的规则创建,初始 `SpecDecisionRequestID` 为空且 `specSource=unresolved`,首趟返回 `true`;当次决策固化后返回 `false`。`stock`、`direct_select`、已固化规格决策、能力缺失及其他组合均返回 `false`。历史兼容的就地 `/reset` 保留 `SpecDecisionRequestID`、目标规格、映射规格和规格决策快照,不能恢复探测资格;映射不完整时必须拒绝,不能进入正式采购阶段。普通 Agent 重试创建新任务并重新取得一次探测资格。Android 不得根据映射是否非空、错误文字或本地判断扩大资格。
Android 规格失败使用五个稳定阶段:`PURCHASE_SPEC_TARGET_NOT_VISIBLE`、`PURCHASE_SPEC_TARGET_AMBIGUOUS`、`PURCHASE_SPEC_SAFE_TARGET_MISSING`、`PURCHASE_SPEC_CLICK_FAILED` 和 `PURCHASE_SPEC_SELECTION_UNCONFIRMED`。`PURCHASE_SPEC_CLICK_FAILED` 的 `errorMessage` 只允许稳定子原因 `root_unavailable`、`target_stale`、`no_clickable_ancestor`、`action_click_false` 或 `unknown`;其他阶段的消息不得包含规格原文、坐标、控件树或截图。只有 `PURCHASE_SPEC_TARGET_NOT_VISIBLE && specResolutionAllowed=true` 可以提交规格探测,其他四态直接提交真实失败,服务端原样保留稳定阶段/子原因。旧 Agent 在资格已用尽后再次提交 `spec_probe_completed` 时,服务端以 `PURCHASE_SPEC_REPROBE_REJECTED` fail-closed,释放租约并保留第一次规格决策,不再冒充新的选择根因或再次派发。无匹配、候选不完整、歧义或 Provider 异常同样使任务失败。`order_result_unknown` 只允许管理员或采购员人工解除,永不自动重派。
@@ -617,12 +617,12 @@ Content-Type: application/json
- 响应返回 `taskId`、`attemptNumber`、`status` 和可选的 `replayed`,不返回规则快照、URL、Token、控件树或截图。
- 设备离线、任务非终态、设备忙、规则不可用或同商品存在活动任务时返回明确冲突,不支持离线排队。
## Agent 受控采购重试(#95、#157)
## Agent 受控采购重试(#95、#157、#217)
普通失败任务的“重试采购”改为就地重跑:
普通失败任务的“重试采购”和替代商品匹配完成后的“继续采购”统一调用新任务接口:
```http
POST /api/agent/v1/purchase-tasks/{taskId}/reset
POST /api/agent/v1/purchase-tasks/{taskId}/retry
Authorization: Bearer <device-token>
Content-Type: application/json
@@ -634,37 +634,35 @@ Content-Type: application/json
```json
{
"data": {
"taskId": 12,
"taskNo": "CG-12",
"attemptNumber": 2,
"status": "pending",
"sourceTaskId": 12,
"sourceTaskNo": "CG-12",
"taskId": 13,
"taskNo": "CG-13",
"replayed": false
}
}
```
- 原任务必须属于当前 Device Token、在最近 30 天内、状态为 `failed`,且是分配给该设备的正式 SYB 采购任务;跨设备或超期按任务不存在处理。
- 任务不得存在 `irreversibleAt`、`orderSubmitRequestId`、PDD 订单号或下单时间。已有任何不可逆证据时返回 `PURCHASE_RETRY_UNSAFE`,提示走“授权重新采购”,不得恢复为待执行。
- 服务端在同一事务锁定任务和设备,确认同一 SYB 商品没有更新任务、设备在线且空闲,然后读取当前服务端采购规则,重新校验 schema、动作安全边界与设备能力。
- 成功时复用原 `purchase_task.id`,只刷新 `ruleSnapshot`、`ruleType`、`ruleSchemaVersion`、`requiredCapabilities`,清除错误、租约和运行守卫并恢复 `pending`。商品、Target、Mapped、价格保护、数量、地址后缀及其他业务快照逐字段保持不变。
- 每次受理创建该任务的新 `pending` attempt 并记录规则哈希;Start 复用该 attempt 转为 `running`,不会重复创建执行记录。相同 `requestId` 重放返回相同 attempt 且 `replayed=true`;不同请求可在任务再次失败后继续重试,不限制次数。
- 采购详情新增 `attemptCount`、可选 `lastFailureCode` 和 `lastFailureMessage`,供 Agent 显示已尝试次数与上次失败原因;不返回规则快照、Token、地址、控件树或截图。
- Android 仍只在服务端 `retryable=true` 且状态为 `failed` 时显示普通“重试采购”。确认文案必须说明任务号不变、使用最新规则重跑、可能产生待付款订单且系统不会支付。
- 规则无效、设备离线/忙、能力不匹配、任务状态变化或同一 SYB 商品已有更新任务时,服务端明确拒绝且不得部分修改任务。
- 来源任务必须属于当前 Device Token、在最近 30 天内、状态为 `failed`,且是分配给该设备的正式 SYB 采购任务;跨设备或超期按任务不存在处理。
- 来源任务不得存在 `irreversibleAt`、`orderSubmitRequestId`、PDD 订单号或下单时间。已有任何不可逆证据时返回 `PURCHASE_RETRY_UNSAFE`,提示走“授权重新采购”,不得创建新任务。
- `AgentRetry → BatchRetry → Create` 保留来源失败任务并创建不同 `purchase_task.id` 的新任务;新任务重新读取当前 SYB、虾皮/PDD 档案、当前采购规则、价格保护和设备能力,重新生成地址后缀,不继承来源任务的旧规格决策。
- 新 SYB 任务按 #215 固定从 `spec_probe` 开始。相同 `requestId` 重放返回同一新任务且 `replayed=true`;不同 requestId 再次请求受同一 SYB 商品最新任务和设备并发门禁约束。
- Android 只在服务端 `retryable=true` 且状态为 `failed` 时显示普通“重试采购”;确认文案和成功反馈必须说明原任务保留、新任务使用当前档案与规则、可能产生待付款订单且系统不会支付。
- 规则无效、当前档案或价格不合格、设备离线/忙、能力不匹配、任务状态变化或同一 SYB 商品已有更新任务时,服务端明确拒绝且不得部分创建。
- Admin 批量重试继续使用相同的新任务语义;替代商品“继续采购”仍在 AgentRetry 前额外验证替换分项与继续采购资格。
既有新建任务接口保留原语义:
历史兼容的就地重置接口仍保留,但 Android 普通重试不再调用:
```http
POST /api/agent/v1/purchase-tasks/{taskId}/retry
POST /api/agent/v1/purchase-tasks/{taskId}/reset
Authorization: Bearer <device-token>
Content-Type: application/json
{"requestId":"<uuid>"}
```
- `/retry` 的 `AgentRetry → BatchRetry → Create` 行为不变:保留来源失败任务并创建新任务,返回 `sourceTaskId`、`sourceTaskNo`、新 `taskId`、新 `taskNo` 和 `replayed`。
- Android 普通“重试采购”不再调用 `/retry`;只有 #132 替代商品规格匹配完成后的“继续采购”继续调用它。Admin 批量重试行为也不变。
- “继续采购”会重新解析替代商品、规格映射与价格并生成新地址后缀;这与普通失败任务保持快照的就地重跑不可互换。
- `/reset` 只允许安全失败、无不可逆证据且不存在更新任务的原任务;它保留原业务快照并刷新当前规则。
- 目标颜色存在但映射颜色为空,或目标尺码存在但映射尺码为空时,必须返回 `PURCHASE_SPEC_MAPPING_REQUIRED`,不得创建 `purchase` attempt 或下发正式采购 payload。
- 两个入口都不执行支付。真机调用可能创建待付款订单,必须先取得人工授权。
## Agent 任务记录范围与同步(#99)