Files
goauto/docs/08-agent-api-contract.md
T

75 KiB
Raw Blame History

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: 75cebdfb2408d35e59d05d5755068b6d8cd126f5 synchronized_at: 2026-09-02T13:24:29Z

MVP 共享 API 契约

本文件是服务端、Web 和 Android 的共享契约事实来源。字段可以在实施工单中补充,但语义变化必须同步更新本文档。

通用约定

  • 正式环境默认只允许 HTTPS;管理员显式设置 GOAUTO_ALLOW_INSECURE_AGENT_HTTP=true 时,全部 /api/agent/v1/** 也接受 HTTP。Android Debug/Release 均可配置 HTTP 或 HTTPS Origin。
  • 每台设备使用独立 Bearer Token。
  • 时间使用 RFC 3339 UTC,金额使用整数分。
  • 所有写接口携带 requestId 并保证幂等。
  • 错误响应包含稳定的 code、可读的 message 和 retryable。

管理端:PDD 商品

POST /api/admin/v1/pdd-products
PATCH /api/admin/v1/pdd-products/{productId}
GET /api/admin/v1/pdd-products
GET /api/admin/v1/pdd-products/{productId}

新增请求只提交 requestId 和 url。服务端规范化 URL、提取 goods_id;无法提取时返回 PDD_GOODS_ID_INVALID,已存在时返回 PDD_PRODUCT_EXISTS 和现有商品 ID。编辑请求完整提交 URL、标题、店铺、数字销量/评价、状态和 specs;人工保存覆盖当前值,但不改变已有任务快照。

specs 是维度数组。维度包含非空 name、role(color / size / other)和非空 values;规格值包含 name、selectable,只有颜色值允许包含非负整数分 priceCent。列表支持 keyword 搜索 goods_id、标题或店铺,并支持 status 筛选。商品状态为 pending、active、disabled。列表项另返回 collectionSelectable、可选的 collectionDisabledReason 和 activeCollectionTaskId,供当前页批量选择;服务端提交时仍必须重新校验。

Android 提交结果的契约不增加字段:服务端在保存任务结果的同一事务更新 PDD 商品最新档案。completed 全量覆盖,completed_partial 合并明确获得的数据,failed 不更新;人工 disabled 状态不会被采集自动改回 active。

管理端:虾皮商品

GET /api/admin/v1/shopee-products
POST /api/admin/v1/shopee-products
GET /api/admin/v1/shopee-products/{productId}
PATCH /api/admin/v1/shopee-products/{productId}
POST /api/admin/v1/shopee-products/{productId}/link-pdd
POST /api/admin/v1/shopee-products/{productId}/restore
POST /api/admin/v1/shopee-products/{productId}/specs/values
DELETE /api/admin/v1/shopee-products/{productId}/specs/values
PUT /api/admin/v1/shopee-products/{productId}/specs/mapping
DELETE /api/admin/v1/shopee-products/{productId}/specs/mapping
POST /api/admin/v1/shopee-products/{productId}/specs/mapping/confirm
POST /api/admin/v1/shopee-products/{productId}/specs/mapping/confirm-exact-matches
POST /api/admin/v1/shopee-products/{productId}/specs/mapping/preview-auto-size
POST /api/admin/v1/shopee-products/{productId}/specs/mapping/auto-match
POST /api/admin/v1/shopee-products/batch-delete

创建与关联/映射相关写操作均提交 requestId 做幂等重放;重放请求返回相同结果并标记 replayed。创建请求提交 shopeeItemId、title、shopName,可选 pddProductId 和 specs;shopeeItemId 重复时返回 SHOPEE_ITEM_ID_EXISTS 和已存在商品 ID。link-pdd 校验 PDD 商品存在且非 disabled,否则分别返回 PDD_PRODUCT_NOT_FOUND 或 PDD_PRODUCT_DISABLED;关联成功后返回值包含 sharedByPddCount,表示当前共用同一 PDD 商品的虾皮商品数。

specs 结构同 PDD 商品的维度/规格值形状,但规格值额外携带 source(import / manual)与可选的 mapping(pddValue、source、status、confidence、reason)。新增规格值固定为 manual 来源;删除规格值仅允许 manual 来源,import 来源返回 SPEC_VALUE_NOT_MANUAL。设置映射时,exact_match 与 ai_match 来源一律写入 pending 状态,与请求体中的 status 无关;只有 manual 来源可以直接写入 confirmed。confirm-exact-matches 仅确认 source=exact_match 且状态为 pending 的映射,不影响 ai_match。该通用入口不因 #188 或 #194 改变;两者只能通过各自独立的服务端写入路径,将唯一确定性 exact_match 或通过高置信度门槛的 ai_match 写入 confirmed。

preview-auto-size 是只读计算接口(使用 POST 触发计算,不写数据库),无需 requestId。它读取当前蝦皮尺码与关联 PDD 的可选尺码,只返回格式统一后唯一确定的匹配;响应含 items[](valueName、可选 pddValue、status=preserved|matched|pending、reason)、pddValues、matchedCount 和 pendingCount。已确认且目标仍存在的映射标为 preserved;无唯一结果标为 pending。Admin 的显式“保存修改”仍通过既有设置/确认接口落库。

auto-match 是 Admin 蝦皮商品详情一键匹配颜色和尺码的独立写入接口。请求体必须提交 UUID requestId 和详情返回的 specContextVersion。服务端在事务外完成必要的 Provider 调用,入事务后重新锁定蝦皮商品与 PDD 商品,复核关联、完整规格上下文、当前可选候选、AI 开关和最新置信度阈值。保留有效的已确认映射;唯一确定结果直接保存为 exact_match + confirmed;置信度达标、理由非空且候选仍有效的 AI 结果保存为 ai_match + confirmed;其余项保持未匹配。响应含 product、items[](dimension、role、valueName、可选 pddValue、status=confirmed|preserved|unmatched、可选 source、confidence、reason)、confirmedCount、preservedCount、unmatchedCount 和 replayed。同一 requestId 幂等重放;Provider 异常或 AI 未启用返回 AI_MATCHING_UNAVAILABLE,关联或规格变化返回 SPEC_CONTEXT_VERSION_STALE,两者都不写入本次结果。

设置映射时,pddValue 必须是关联 PDD 商品同角色下当前可选的原始规格标签,否则返回 INVALID_REQUEST。PDD 重新采集后旧目标消失时,Admin 标记失效;采购预检与任务创建返回 PURCHASE_SPEC_MAPPING_REQUIRED 和“规格匹配已失效,请重新选择 PDD 规格”,不得把旧标签下发给 Agent。

batch-delete 提交 requestId 和 ids(1~500 个),逐条校验引用后返回每条的 status(deleted / skipped)与 reason;引用检查覆盖 SYB 明细(#41)与采购任务(#33/#34),两张表落地前恒不阻塞删除。restore 恢复一条已软删除商品,恢复后原有 PDD 关联与规格映射保持不变。列表接口 status=deleted 筛选已删除商品,默认只返回存活商品。

管理端:SYB 商品明细

GET /api/admin/v1/syb-products
GET /api/admin/v1/syb-products/{productId}
POST /api/admin/v1/syb-products/import
GET /api/admin/v1/syb-products/sync-runs
GET /api/admin/v1/syb-products/sync-runs/{runId}
POST /api/admin/v1/syb-products/{productId}/reparse
POST /api/admin/v1/syb-products/reparse-batch
PATCH /api/admin/v1/syb-products/{productId}/correction

import 仅允许管理员调用。请求体提交 dateFrom、dateTo 后创建持久化后台任务并立即以 202 返回 runId 和 status=running;关闭弹窗、刷新或离开页面不影响任务。没有启用店铺时必须在读取凭据、登录、验证码 OCR 和任意 SYB 网络请求之前返回 422。任意时刻只能有一条 running 记录,内存锁与数据库唯一执行槽共同阻止单进程和跨进程重复导入;冲突时返回正在执行任务的日期范围。

sync-runs 列表支持 page、pageSize、status、dateFrom、dateTo,详情返回日期范围、状态(running / succeeded / failed / interrupted)、处理天数、货运单/明细/新增/覆盖数量、店铺准入与跳过数量、店铺筛选快照哈希、按店铺的 accepted / skipped 统计、操作人和起止时间。列表和详情对已登录角色只读开放。服务启动时遗留的 running 任务改为 interrupted;中途失败或中断已经写入的数据保留,重新导入仍按唯一键覆盖。

列表返回结构化字段(orderCode、shopeeItemId、productTitle、targetColor、targetSize、quantity、unitPriceCent、imageUrl、parseStatus、parseNote、manuallyConfirmed),不含原始 JSON;keyword 匹配订单号、虾皮商品ID 或商品标题,parseStatus 筛选 success/uncertain/failed。详情额外返回 rawJson(原始 details[] 元素,未做任何改写)。

reparse 与 reparse-batch 只读取已保存的 rawJson 重新执行解析规则,不请求 SYB 接口;请求体 force 为 true 时才覆盖 manuallyConfirmed 的行,默认跳过并在批量结果中标记 skipped_manual。批量结果逐条返回 outcome(reparsed / skipped_manual / unchanged)与解析状态变化,ids 中任何一个不存在都会使整个请求返回错误(与虾皮商品批量删除的"部分成功"语义不同:ID 不存在通常是操作员选错了页面)。

correction 提交 targetColor、targetSize,覆盖解析结果并标记 manuallyConfirmed=true;非空的人工修正与解析成功同等可信,会合并进已关联的虾皮商品档案。

管理端:SYB 店铺

GET    /api/admin/v1/syb-shops
POST   /api/admin/v1/syb-shops
PATCH  /api/admin/v1/syb-shops/{shopId}/name
PATCH  /api/admin/v1/syb-shops/{shopId}/enabled
DELETE /api/admin/v1/syb-shops/{shopId}
POST   /api/admin/v1/syb-shops/discover

列表支持 keyword、enabledOnly、page 和 pageSize,返回店铺原名、规范化名称、启用状态及分页汇总。写操作由 go-admin/Casbin 仅授权管理员;其他角色只读。删除为软删除,不清理已经导入的数据。

discover 提交日期范围,从 SYB 真实货运单列表发现店铺,返回 shopName 和 status(new / exists);发现只读,不自动新增。店铺比较固定执行首尾去空白、全角/半角统一和忽略大小写。同步先完成原始列表完整性校验,再按一次性启用店铺快照过滤;明细响应中的店铺名为空或不在快照内时不得写入。

管理端:采集规则

POST /api/admin/v1/collection-rules
PATCH /api/admin/v1/collection-rules/{ruleId}
DELETE /api/admin/v1/collection-rules/{ruleId}
GET /api/admin/v1/collection-rules
GET /api/admin/v1/collection-rules/templates/pdd-product-detail-v1

规则只有 name 和 content 等当前值,创建成功后立即可用。删除为软删除;已删除规则不能创建新任务,已有任务继续使用任务内快照。

模板接口返回服务端内置的完整 v2 content,以及浏览器包名、别名数量/长度、超时、遍历上限和规格面板滑动动作的可编辑范围。管理端以安全表单修改这些参数,并显示只读 JSON 预览;模板中的采集器 ID、价格解析器、页面根证据、动作类型和语义目标不可在界面中改写。最终创建或更新请求仍由服务端按完整 v2 契约重新校验,不能依赖前端校验。

content 支持原有 schemaVersion: 1 线性步骤和 schemaVersion: 2 类型化采集器契约。v1 契约如下:

{
  "schemaVersion": 1,
  "packageName": "com.xunmeng.pinduoduo",
  "steps": [
    {
      "id": "extract-title",
      "page": "product-detail",
      "activityName": "com.xunmeng.pinduoduo.activity.NewPageActivity",
      "action": "extract",
      "selector": {"resourceId": "com.xunmeng.pinduoduo:id/title"},
      "field": "title",
      "timeoutMs": 5000
    }
  ]
}
  • action 只允许 wait、click、input、back、extract;除 back 外都必须有非空 selector。
  • selector 可组合 resourceId、text、contentDescription、className、clickable,执行操作前必须唯一命中;不唯一时以 RULE_AMBIGUOUS 失败,不猜测目标。
  • 点击步骤仍以唯一命中的节点作为语义证据;若文字节点本身不可点击,只允许沿其父链点击最近的 clickable 容器,不搜索兄弟节点或相近候选。父链不存在可点击容器时以 RULE_ACTION_FAILED 失败。
  • 每一步可以用 packageName 覆盖根级默认值;只允许拼多多、受支持浏览器和 Android 系统选择器包。
  • activityName 可声明该步骤允许执行的精确 Activity;商品详情页规则应同时校验 PDD 包名、目标 Activity 和唯一控件,避免登录页等同包页面误判为商品页。
  • input 必须有 value,extract 必须有 field;单步 timeoutMs 范围为 100~30000 毫秒。
  • optional: true 仅表示单步超时后继续,适合不同 ROM 可能不存在的浏览器/系统“打开”确认层;多匹配和动作执行失败仍立即失败。
  • 规则解析阶段按用途校验文字:支付、付款、提交订单等可以出现在只读识别字段中,但不能成为点击目标;修改地址、收货地址在可配置点击目标和只读识别字段中都拒绝。完整控件树只在 Android 内存中用于匹配,不保存也不上传。
  • 浏览器和 Android 系统包中的 click 只允许精确目标 打开拼多多APP、打开拼多多 App 或 打开;其它点击在规则解析阶段拒绝。Android 先以浏览器打开服务端已规范化的 PDD URL,再由这些受控步骤进入拼多多。

v2 PDD 商品详情规则

v2 完整示例见 PDD 商品详情规则。规则声明:

  • ruleType: pddProductDetail。
  • navigation.steps:只负责浏览器和系统确认层;不能点击 PDD 页面。
  • pageEvidence:精确的 PDD 包名、Activity 和非空节点证据。
  • collector.collectorId: pddProductDetailV1:引用 Agent 中经过测试的类型化能力,不下发可执行代码。
  • hooks.afterSpecPanelOpen:固定阶段的安全动作;当前只允许对语义目标 specPanel 执行 swipe,方向为上下左右、单动作次数 1~5、等待 0~2000 毫秒,单阶段最多 8 个动作。
  • pageRecovery.transientSoldOut:商品详情页出现规则配置的 exactText,或顶部区域出现 fallbackTopText 且缺少主商品规格/购买强证据时,允许对商品页执行一次有界恢复;内置默认值仍为“商品已售罄”与“相似商品”,默认下拉 2 次、间隔 1000 毫秒。推荐卡片自身的标题、价格和销量不属于主商品证据。fallbackTopText 缺省时 Agent 使用“相似商品”兼容旧快照。Agent 必须声明无障碍手势能力,且只把系统回调完成的下拉计为成功。规格面板内的 SKU 售罄不触发。恢复后仍异常时失败码为 PDD_GOODS_SOLD_OUT。
  • navigationRecovery.reopenBrowser:首次跳转未通过精确商品详情页证据时,Agent 重新显式打开任务 urlSnapshot 并重放安全导航步骤;最多恢复 1 次,重开后等待 500~5000 毫秒。登录、验证码、风控、无效链接和进入详情页后的采集失败不触发。
  • collector.dimensionAliases、collector.textAliases、timeoutsMs 和 limits:分别管理颜色/尺码分类别名、只读页面识别文案、超时和遍历/SKU 上限。textAliases 可选;旧规则快照缺省时 Agent 使用当时内置默认值。新规则可在 specPanel、dimension、selection、review、soldOut、purchase 六组中配置面板、维度、选择摘要、评价入口、主商品证据和购买入口文字;每个数组为 1~20 项、单项不超过 30 个 Unicode 字符、不得有首尾空白或重复项。维度分类只依据规则的 dimensionAliases,代码不再以“颜色/款式/尺码/尺寸”字面量兜底,因此规则可以真实收窄。订单和支付文字允许作为只读识别证据;修改地址、收货地址 始终拒绝。用于阻止创建订单、确认订单、支付和付款控件成为采集点击目标的拒绝清单固定保留在 Agent,不能由规则覆盖。

例如规格面板打开后向上滑动两次,只修改规则:

{"hooks":{"afterSpecPanelOpen":[{"action":"swipe","target":"specPanel","direction":"up","count":2,"settleMs":350}]}}

Agent 使用可扩展的类型化动作注册表,而不是任意脚本。采集规则不能创建订单;采购规则可以引用单独审核的创建订单能力。当前契约不实现支付动作,pay / payment 动作仍被拒绝;订单和支付文字只允许作为只读识别信号,不能成为点击目标。

Android 的 pddProductDetailV1 采集器执行以下固定流程:

  1. 以规则中的包名、精确 Activity 和节点选择器验证商品详情页,并持续识别登录、验证码、风控和失效商品页面。
  2. 在商品页有限次纵向查找安全规格入口;规格面板必须同时具备规格维度和确认摘要或可滚动区域等强证据。
    • 入口候选排除评价、评论、晒单和问答上下文,不使用全页面“规格词 + 任意数字”猜测;底部兜底必须命中真实购买/拼单文字及其可点击链路,且排除提交订单与支付语义。
    • 商品评价页与详情页共用 NewPageActivity 时,首次误触允许全局返回、重新验证详情页并重新定位一次;第二次误触停止。点击无变化、误入评价页和面板证据不匹配分别返回 SPEC_ENTRY_CLICK_NO_EFFECT、SPEC_ENTRY_OPENED_REVIEW、SPEC_PANEL_EVIDENCE_NOT_MATCHED。
  3. 规格面板以有界稳定读取恢复到顶部;横向颜色容器和纵向面板容器必须由已识别规格节点的祖先关系锁定,不能只按屏幕中最大滚动区域猜测。
  4. 颜色先归左,再按视觉行执行左到右、右到左交替的蛇形遍历;每次点击都在最新无障碍树中重新定位唯一文字控件。点击后必须取得目标选中、已选摘要或规格面板选中状态/价格变化证据,等待短暂刷新后连续读取相同价格;其他颜色残留选中标记不能阻塞当前颜色,同价合法,但动作无效果时不能沿用点击前旧价格。结构化轨迹只记录短颜色标签、点击结果、证据类型、价格采样和 selection_not_confirmed、price_not_found、price_not_stable 原因。
  5. 滚动优先调用目标容器的无障碍前进/后退动作;回退坐标手势时必须等待系统完成回调。视口签名包含规格文字和 bounds,连续稳定后才确认到边。
  6. 尺码仅通过有限次纵向滑动读取,不点击尺码;首次定位后保存容器和选项结构,标题滚出屏幕后仍可续页。颜色价格展开到该颜色下的可用尺码 SKU。
  7. 缺失颜色价格、尺码、第三规格维度或超过 SKU 上限时提交有界的 completed_partial;不猜测缺失值。采集期间离开商品页或出现验证码、登录、风控时明确失败。

无障碍节点只投影为 Agent 进程内的瞬时不可变模型,不序列化、不上传、不写入文件。完整状态机接入后 Agent 才上报 collector.pdd.product-detail.v1。

管理端:AI 规格匹配设置

GET  /api/admin/v1/ai-matching-settings
PUT  /api/admin/v1/ai-matching-settings
POST /api/admin/v1/ai-matching-settings/test

这是采购规格匹配的单例配置,Provider 固定为 openai_compatible。管理员可读取、保存和测试连接;保存请求包含 enabled、baseUrl、model、timeoutSeconds 与 apiKey。timeoutSeconds 默认 15,允许范围为 3~600 秒。根据 #62 已确认的内部部署例外,管理员 GET 响应会返回已保存的明文 apiKey,供下次查看和替换;采购员 GET 只返回 enabled,不能读取 Provider、地址、模型或 API Key,也不能保存或测试。

Base URL 支持内网或公网的 http://、https://,不限制为局域网地址。服务端只在管理员点击测试或采购规格没有唯一确定性结果时,才向 Provider 发送目标颜色/尺码和 PDD 可选颜色/尺码;请求中不包含控件树、截图、地址、账号或订单。HTTP 不加密 API Key 的传输,生产环境建议使用 HTTPS。服务端禁止跟随 Provider 重定向,Provider 返回的颜色/尺码必须逐字等于当前候选原始标签,否则按无匹配处理。

管理端:采集任务

POST /api/admin/v1/collection-tasks
POST /api/admin/v1/collection-tasks/batch
GET /api/admin/v1/collection-tasks
GET /api/admin/v1/collection-tasks/{taskId}
POST /api/admin/v1/collection-tasks/{taskId}/reset
DELETE /api/admin/v1/collection-tasks/{taskId}

创建请求:

{
  "requestId": "uuid",
  "pddProductId": "product-id",
  "ruleId": "rule-id",
  "deviceId": "device-id-or-null"
}

服务端在事务中复制当前 url、goods_id 和完整规则内容到任务。同一商品已有 pending 或 running 任务时返回 PDD_PRODUCT_TASK_ACTIVE。

批量创建请求:

{
  "requestId": "batch-uuid",
  "pddProductIds": [1, 2, 3],
  "ruleId": 2,
  "deviceId": null
}

pddProductIds 必须包含 1~100 个不重复的有效 ID。一个批次统一使用当前规则和可选设备;每个商品独立调用单任务创建事务并固化自己的 URL、goods_id、规则和设备快照。批次 requestId 与商品 ID 派生稳定的逐项创建标识,同一请求重放返回原任务,不重复创建。

HTTP 200 响应返回 successCount、failureCount 和按请求顺序排列的 items。成功项包含 pddProductId、success: true、taskId 和可选 replayed;失败项包含 success: false、稳定 code 和普通人可理解的 message。商品已停用返回 PDD_PRODUCT_DISABLED,活动任务冲突返回 PDD_PRODUCT_TASK_ACTIVE。逐项失败不回滚其它成功项;请求 JSON、批次 UUID、重复商品或数量上限无效时整体返回 INVALID_REQUEST。

重置只允许 completed、completed_partial 或 failed:

  • 保留 pddProductId、deviceId、urlSnapshot、goodsIdSnapshot、ruleId 和 ruleSnapshot。
  • 删除全部规格维度、规格值、SKU 和 SKU 值关联。
  • 清空标题、店铺、销量、评价、错误与执行时间。
  • 状态恢复为 pending。

running 禁止重置和删除。失败任务允许删除后重新创建。

reset 请求体和失败任务 DELETE 请求体均为 {"requestId":"uuid"}。只允许删除 failed;重置允许 completed、completed_partial、failed。二者均按 requestId 幂等。

Android:注册与心跳

POST /api/agent/v1/register
POST /api/agent/v1/heartbeat

客户端首次启动生成并持久化 installId,直接提交设备信息;服务端收到合法请求即创建或更新设备,不要求注册码、后台审核或人工确认。installId 在服务端唯一,硬件标识不能作为替代唯一键。响应返回 deviceId、Device Token 和心跳间隔,客户端安全保存 Token,服务端只保存其不可逆摘要。后续用 installId 保持同一安装实例的设备身份;心跳上报设备状态和 currentTaskId,服务端据此判断在线/空闲,但任务领取仍以数据库原子约束为准。

注册请求:

{
  "requestId": "uuid",
  "installId": "uuid",
  "name": "OPPO-PKG110",
  "manufacturer": "OPPO",
  "model": "PKG110",
  "androidVersion": "16",
  "agentVersion": "0.1.0",
  "pddVersion": "7.72.0",
  "capabilities": ["rule.schema.v2", "action.swipe.v1", "collector.pdd.product-detail.v1"]
}
  • 新 installId 立即创建设备并返回一次 deviceToken;响应带 Cache-Control: no-store。
  • 相同 requestId 重放只幂等返回原 deviceId,不再次返回 Token,也不覆盖设备信息。
  • 已存在 installId 的新请求必须携带该设备的 Bearer Token,认证成功后幂等更新设备信息,不轮换 Token。
  • 缺少或使用错误/已吊销 Token 返回 HTTP 409 和 DEVICE_INSTALL_ID_CONFLICT。
  • 生产模式默认只接受 TLS。只有服务部署在可信反向代理之后并显式设置 GOAUTO_TRUST_FORWARDED_PROTO=true 时,服务端才接受代理的 X-Forwarded-Proto: https;不得在服务直接暴露公网时开启。管理员显式设置 GOAUTO_ALLOW_INSECURE_AGENT_HTTP=true 后,注册、心跳、采集、采购、Agent 版本检查与下载等全部 /api/agent/v1/** 可直接使用 HTTP;Device Token、任务内容、设备状态和执行结果会以明文经过链路。注册接口仍有单实例、按直连来源 IP 的基础限流,网关仍须设置共享限流。该开关不影响管理端和第三方服务的 HTTPS 约束。
  • capabilities 最多 32 项,使用小写的版本化能力名。旧 Agent 可以不提交该字段并继续执行 v1;v2 任务只能由包含规则所需全部能力的设备领取。

管理员设备动作:

POST /api/admin/v1/devices/{deviceId}/disable
POST /api/admin/v1/devices/{deviceId}/token/revoke

两者都要求 go-admin 管理端认证与角色授权。吊销同时停用设备;重新启用和重新签发 Token 不在本工单范围。

心跳请求使用设备 Bearer Token:

{
  "requestId": "uuid",
  "currentTaskId": "task-id-or-null",
  "capabilities": ["rule.schema.v2", "action.swipe.v1"]
}

服务端校验 currentTaskId 必须与该设备唯一的执行中任务一致;执行中任务统一覆盖采集任务的 running,以及采购任务的 running / order_submit_started。不一致返回 HTTP 409 和 DEVICE_TASK_MISMATCH,不更新心跳。响应返回服务端确认的 currentTaskId、online、由两类任务统一派生的 busy、服务端时间和下一次心跳间隔。相同 requestId 重放不推进心跳时间;若数据库异常存在跨任务域同时运行,服务端返回内部一致性错误,不静默选择其中一条。

默认心跳间隔 15 秒、离线阈值 45 秒、扫描间隔 15 秒。超过阈值后设备标为 offline:运行中的采集任务和未进入订单提交边界的采购任务清除租约与运行 guard,转为 failed,对应采购 attempt 同步失败并结束,错误码为 DEVICE_OFFLINE,且不自动重试或换机。采购任务已处于 order_submit_started 时必须转为 order_result_unknown,释放设备/账号运行槽并等待人工核对,禁止自动重派或再次点击创建订单。设备重新发送合法心跳后可以恢复 online,但失败或结果未知的任务不会自动恢复。

Android:获取、领取和开始任务

GET  /api/agent/v1/tasks/next
POST /api/agent/v1/tasks/{taskId}/claim
POST /api/agent/v1/tasks/{taskId}/start

claim 和 start 请求体都为 {"requestId":"uuid"}。相同 requestId 重放返回首次成功后的当前任务载荷,不延长租约、不增加租约版本。next 没有可用任务时返回 HTTP 204。

领取规则:

  1. 指定设备的任务只能由指定设备领取。
  2. deviceId 为空的任务可由在线且没有活动任务的设备领取。
  3. claim 在一个数据库事务中设置设备和租约,竞争失败返回 TASK_ALREADY_CLAIMED。
  4. 设备已有活动任务时返回 DEVICE_BUSY。
  5. 指定设备和领取设备必须具备规则快照要求的全部能力;不兼容时返回 DEVICE_CAPABILITY_MISMATCH。未指定设备任务会跳过不兼容设备。

claim 保持任务为 pending,写入设备、两分钟领取租约和递增的 leaseVersion;start 只接受当前设备持有的有效租约,将状态原子改为 running、记录开始时间并续租。Android 进程还必须以本地互斥锁确保同一时刻只有一个任务进入执行器。

任务载荷包含:

{
  "taskId": "task-id",
  "pddProductId": "product-id",
  "urlSnapshot": "https://mobile.yangkeduo.com/goods.html?goods_id=123",
  "goodsIdSnapshot": "123",
  "ruleId": "rule-id",
  "ruleSnapshot": {},
  "timeoutSeconds": 120
}

规则已经软删除不影响载荷和执行。

Android:提交结果

POST /api/agent/v1/tasks/{taskId}/result
{
  "requestId": "uuid",
  "status": "completed_partial",
  "product": {
    "pddGoodsId": "123",
    "title": "商品标题",
    "shopName": "店铺名",
    "salesText": "已拼5872件",
    "reviewCount": 64
  },
  "dimensions": [
    {"key": "color", "name": "颜色", "values": ["黑色", "白色"]},
    {"key": "size", "name": "尺码", "values": ["M", "L"]}
  ],
  "colorPrices": [
    {"color": "黑色", "priceCent": 1000},
    {"color": "白色", "priceCent": 1200}
  ],
  "skus": [
    {"specs": {"color": "黑色", "size": "M"}, "priceCent": 1000, "available": true}
  ],
  "missing": ["白色/L"]
}

服务端验证任务当前设备和状态后,在一个事务中更新 collection_task 结果字段并重建结果子表。终态任务拒绝不同内容的再次提交;相同 requestId 幂等返回原响应。 规格值规范化属于服务端最终契约防线:

  • size 规格值只允许剥离位于文本尾部的 ¥ / ¥ 数字价格片段,括号说明、体重区间和其他规格身份文本原样保留;Android 与服务端使用相同语义。
  • 服务端在结果完整性校验前执行规范化。规范化后为空、同维度重复、仍残留货币符号或无法由最小安全规则确定时,整维不接受,并原子丢弃引用该维度的 SKU;color 被拒时同时丢弃 colorPrices。
  • 服务端强制将任务收敛为 completed_partial,并在 missing 增加稳定值 spec_dimension_invalid:,例如 spec_dimension_invalid:size。客户端应把它展示为对应规格维度无法安全采集,不得视为完整档案,也不得自行猜测或补齐候选。
  • 被拒维度不得以污染值覆盖商品已有档案;历史任务和既有商品数据不自动清洗,须在修复后的 Agent 上重新采集。

Android:提交失败

POST /api/agent/v1/tasks/{taskId}/fail
错误码 含义 默认重试
DEVICE_OFFLINE 执行中连接中断 否
ACCESSIBILITY_NOT_READY 无障碍未绑定 否
PDD_LOGIN_REQUIRED PDD 登录失效 否
PDD_CAPTCHA_REQUIRED 验证码或人机验证 否
PDD_RISK_CONTROL 风控页面 否
PDD_LINK_INVALID 商品链接无效、商品不存在或已下架 否
PDD_GOODS_SOLD_OUT 商品页一次性下拉恢复后仍显示商品已售罄,或恢复动作失败 否
PDD_DETAIL_ENTRY_FAILED 一次性重开浏览器后仍未进入精确 PDD 商品详情页 否
RULE_NOT_MATCHED 找不到唯一控件或页面 否
SPEC_ENTRY_CLICK_NO_EFFECT 规格入口点击后页面没有变化 否
SPEC_ENTRY_OPENED_REVIEW 规格入口误进入商品评价页且受控恢复失败 否
SPEC_PANEL_EVIDENCE_NOT_MATCHED 页面发生变化,但规格面板强证据不足 否
RULE_AMBIGUOUS 规则同时匹配多个控件 否
TASK_ALREADY_CLAIMED 未指定任务已被其他设备领取 否
DEVICE_BUSY 设备已有活动任务 否
DEVICE_CAPABILITY_MISMATCH 设备缺少任务规则要求的版本化能力 否

采购任务共享契约(#33、#34、#36、#42、#44、#53、#67、#164、#166)

本节固定采购域的数据和接口边界;purchase_task、purchase_task_attempt、规则校验、服务端 HTTP 状态机、Admin 已有任务管理、SYB 创建入口、Android 演练执行及 #36 正式地址/订单动作已经落地。#36 尚未获得真机创建订单授权和验收。当前项目没有自动支付动作、入口或测试;后续支付能力必须另行建单并通过显式能力位、服务端开关、金额上限和人工授权门禁。

任务与快照

  • executionMode 只能是 rehearsal 或 live,创建后不可修改。
  • 正式 live 任务必须引用一条 syb_product;无副作用的 rehearsal 可以不引用 SYB。
  • 创建时固化 SYB、蝦皮订单号、虾皮商品、PDD 商品、目标规格、映射规格、数量、价格区间、币种、URL、goods_id、规则和可选 PDD 账号引用。正式任务的 shopeeOrderNoSnapshot 来自创建时的 syb_product.order_code;同一订单号可对应多条任务,之后来源商品修改不会改写任务快照。
  • Android 无法可靠读取当前 PDD 账号,因此 pddAccountId 和 pddAccountRefSnapshot 均可空。已知账号时参与账号级串行,未知时不阻止任务。
  • 地址后缀由任务 ID 唯一确定为 _cg{taskId};#34 创建任务时必须在同一事务内回写快照。
  • 一个任务最多对应一个 PDD 订单;重新采购必须新建任务,旧任务和旧订单保留。

状态集合:

状态 含义 是否占用 SYB 活动槽
pending 待执行 是
spec_probe_pending 第一趟探测结束,待服务端固化规格并重新派发 是
running Agent 执行中 是
rehearsal_completed 演练安全结束,未改地址、未创建订单 否
order_submit_started 不可逆标记已落库,只能核单,禁止再次点击 是
order_created 已取得唯一 PDD 订单号和下单时间 是
order_result_unknown 无法确认是否下单,必须人工处理 是
failed / cancelled 终态 否

同一个 sybProductId 最多一个占用活动槽的任务;一个设备最多一个执行中的采购任务;已知的同一个 PDD 账号最多一个执行中的采购任务。终态历史不删除。 purchase_task.task_type 固定为 syb_order 或 stock,旧记录迁移为 syb_order。stock 的 sybProductId、shopeeProductId 及对应身份快照为空,颜色和尺码必须逐字命中当前 active PDD 档案中的可选值,specSource=direct_select;同商品的多个活动备货任务可以共存,但执行期仍受设备和可选账号互斥约束。stock 不进入 SYB 批量预检/批量重试、Agent 重试、商品替换、重新采购授权或 SYB 回填;失败后返回“重新创建备货采购”的可读提示。

规则快照与能力

采购规则是 JSON 对象。#53 在既有 schema v1 中增加受限参数,不引入任意脚本:

{
  "schemaVersion": 1,
  "ruleType": "pddPurchase",
  "requiredCapabilities": ["purchase.rehearsal.v1"],
  "actions": [
    {
      "type": "openProduct",
      "textAliases": ["打开拼多多APP", "打开"],
      "waitAfterMs": 1000
    },
    {
      "type": "openSpecPanel",
      "textAliases": ["选择规格"],
      "swipeAfter": {
        "direction": "up",
        "count": 2,
        "durationMs": 500,
        "intervalMs": 1000
      }
    },
    {
      "type": "probeSpecs",
      "dimensionAliases": {
        "color": ["颜色分类", "颜色", "款式", "颜色款式", "花色", "组合"],
        "size": ["尺码", "尺寸", "套餐", "参考分类"]
      }
    },
    {"type": "selectSpec"},
    {"type": "setQuantity"},
    {"type": "verifyUnitPrice"},
    {"type": "verifyOrderSummary"}
  ]
}

安全动作参数矩阵:

action textAliases waitAfterMs swipeAfter dimensionAliases
openProduct 是 是 是 否
verifyProduct 是 是 否 否
openSpecPanel 是 是 是 否
selectSpec 是 是 是 否
setQuantity 是 是 否 否
verifyUnitPrice 是 是 否 否
verifyOrderSummary 是 是 否 否
probeSpecs 是 是 是 是

参数规则:

  • dimensionAliases 仅允许配置在 probeSpecs,并同时提供 color 与 size;每组包含 1~20 个互不重复、无首尾空白的非空文字,每项最多 30 个 Unicode 码点。它只用于只读规格维度探测,不参与 selectSpec 点击;旧规则快照缺省时使用 Agent 内置兼容别名。
  • textAliases 省略时使用 Agent 对该类型化动作的内置语义目标;显式提供时必须包含 1~16 个互不重复、无首尾空白的非空文字,每项最多 64 个字符。
  • 文字候选按控件文字或内容描述精确匹配;候选合并后必须唯一命中。点击动作只允许点击唯一文字节点或其最近的可点击父容器,不允许模糊匹配、猜测相近候选、改点兄弟节点。
  • 动作 textAliases 不能包含地址修改、创建/提交订单、订单号或支付相关文字,防止用安全 action 绕过危险动作类型和能力门禁。只读识别字段使用独立校验:允许订单和支付证据,仍拒绝修改地址、收货地址,并沿用各字段声明的数量、长度、去重和空白限制。
  • waitAfterMs 表示动作成功后的等待时间,范围为 0~30000 毫秒;省略时为 0。
  • swipeAfter 表示动作成功后执行一个有限滑动计划。direction 只能为 up / down / left / right,count 为 1~10,durationMs 为 100~2000,intervalMs 为 0~5000 且省略时为 0。
  • 未在矩阵中授权的 action/参数组合、未知字段、空候选和越界值一律拒绝。updateShippingAddress、createOrder、readOrderResult 等正式动作在其独立高风险契约完成前不接受上述参数。
  • 旧的仅含 actions[].type 的规则继续有效:候选使用 Agent 内置语义,等待为 0,不执行动作后滑动。
  • 服务端保存创建任务时收到的完整原始规则快照;规则后来更新为规则 B,不会改变已有任务中的规则 A 快照。

演练规则必须包含 purchase.rehearsal.v1,并且不能包含 updateShippingAddress、createOrder、readOrderResult。正式规则必须包含 purchase.live.v1;改地址和创建订单还分别要求 purchase.address-update.v1、purchase.order-create.v1。probeSpecs 要求 purchase.spec-probe.v1。任意模式下,尚未实现的 pay、名称包含 payment 的动作以及未知动作一律拒绝;服务端不下发任意脚本。

管理端接口

方法 路径 幂等键 / 说明
GET /api/admin/v1/purchase-tasks 分页列表;可按任务 ID、蝦皮订单号 shopeeOrderNo、状态、模式、SYB 商品 ID 和 PDD 订单号筛选
GET /api/admin/v1/purchase-tasks/{taskId} 只读任务详情与 attempt 历史;不返回规则原文、Token、凭据、完整地址、控件树或截图
POST /api/admin/v1/purchase-tasks requestId;单条创建
POST /api/admin/v1/purchase-tasks/stock 创建备货采购;requestId、executionMode、pddProductId、可选 deviceId / pddAccountId、color、可选 size、quantity、minUnitPriceCent、maxUnitPriceCent。服务端使用内置规则并从所选颜色归档派生参考价;相同 requestId 幂等返回原任务
POST /api/admin/v1/purchase-tasks/batch-preview sybProductIds(1~100)和可选 deviceId;逐条返回采购是否可创建、价格区间、原因和下一步,并独立返回 PDD 采集资格 collectionEligible / collectionDisabledReason 与规格匹配资格 aiMatchEligible / aiMatchDisabledReason,不创建任务。只有目标颜色和尺码已保存为确认映射,且共同命中最近一次成功或部分成功采集中的完整可售 SKU 组合时,eligible 才为 true;只在内存中得到的确定性结果不算采购就绪
POST /api/admin/v1/purchase-tasks/batch-spec-match sybProductIds(1~100);仅处理服务端预检 aiMatchEligible=true 的明细,其他明细按具体禁用原因返回 skipped,并逐条返回 auto_confirmed / pending / failed / skipped。资格要求 SYB 采购规格解析成功、蝦皮与 PDD 关联完整、PDD 当前规格可用,且最近一次成功或部分成功采集存在完整可售 SKU 组合证据;未人工确认的 parse_status=uncertain 不进入自动确认,manuallyConfirmed=true 与 parse_status=success 同等视为可信规格。唯一确定性 exact_match 不调用 Provider,直接通过独立路径保存为 confirmed;其余结果只有 ai_match 置信度达到 autoConfirmMinConfidence、理由非空、返回值属于当前候选且颜色+尺码命中同一个可售 SKU 组合时才保存为 confirmed。低置信度、无效组合和 Provider 异常不改写现有映射
POST /api/admin/v1/purchase-tasks/batch 批次 requestId、sybProductIds(1~100)和可选 deviceId;每条派生稳定幂等键并独立创建,部分失败不回滚成功项
POST /api/admin/v1/purchase-tasks/{taskId}/authorize-repurchase 一次性授权;创建新任务后自动消耗
POST /api/admin/v1/purchase-tasks/{taskId}/payment-review paid 或 unpaid,管理员和采购员可操作
POST /api/admin/v1/purchase-tasks/{taskId}/writeback-candidate 人工选择回填候选;后选的已支付订单覆盖同一 SYB 明细的旧选择
POST /api/admin/v1/purchase-tasks/{taskId}/cancel 人工取消;执行中和已开始提交订单时禁止直接取消
POST /api/admin/v1/purchase-tasks/{taskId}/resolve-unknown 人工把结果未知解除为已创建订单或已取消
POST /api/admin/v1/purchase-tasks/{taskId}/spec-decision 固化第一趟规格探测决策;同一 attempt 不可改写

Admin 列表与详情由 #35 实现;#67 增加 shopeeOrderNoSnapshot 的列表/详情返回和 shopeeOrderNo 筛选,空快照返回空字符串并由页面显示“—”,不与 pddOrderNo 混用;#44 在 SYB 商品列表提供单条/当前页批量创建入口。页面预检只负责提前解释,batch 提交时仍逐条执行现有单任务事务和活动任务、订单、支付、设备能力门禁;返回项包含任务号,或失败原因与下一步建议。只有管理员和采购员可以调用。

创建请求的价格保护使用整数分:referenceUnitPriceCent、minUnitPriceCent、maxUnitPriceCent 和 currency。执行时以 PDD App 实际单价校验;低于最小值或高于最大值均返回普通人可理解的价格越界错误,不考虑优惠券,不以订单总价替代单价判断。批量创建不接受浏览器提交价格:服务端读取当前采购规则的可选 priceGuard,minRatio 允许 0.1~1.0,maxRatio 允许 1.0~3.0 且前者不得大于后者;字段缺省时使用 0.2 / 1.5。最低边界按“最低颜色价 × minRatio”向下取整,最高边界按“最高颜色价 × maxRatio”向上取整,均使用定点整数计算以保持旧默认逐分一致;没有可用颜色价格时逐条拒绝。SYB/Shopee 的 TWD 售价不参与 PDD 采购价格保护。

Android 接口(由 #34 实现)

方法 路径 说明
GET /api/agent/v1/purchase-tasks/next 返回与设备能力兼容的指定任务或空闲任务
POST /api/agent/v1/purchase-tasks/{taskId}/claim requestId 原子领取,并建立设备/可选账号租约
POST /api/agent/v1/purchase-tasks/{taskId}/start 创建不可变 taskAttemptId
POST /api/agent/v1/purchase-tasks/{taskId}/order-submit-started 创建订单前先落不可逆标记;演练任务永远拒绝
POST /api/agent/v1/purchase-tasks/{taskId}/result 请求体携带 taskAttemptId 和 requestId;幂等提交演练、规格探测、订单或失败结果

创建阶段需要外部 AI 的任务会先以 pending 与持久匹配工作项同事务创建并立即返回 Admin。工作项处于 pending、running、retry_wait 或 manual_required 时,next 不返回该任务,直接调用 claim 或 start 也会返回状态冲突。匹配完成后 Agent 仍只接收服务端固化的精确 PDD 原始标签;Android 接口和请求体不新增 AI、候选或人工决策字段。

结果提交至少关联 taskId、taskAttemptId、deviceId、规则快照哈希和结构化结果。相同 attempt 的相同结果重复提交返回同一事实;不同内容拒绝覆盖。慢路径第一趟提交规格后释放设备与已知账号租约,任务进入 spec_probe_pending;服务端先使用已确认人工映射,否则对实时/档案可选规格做繁简、空白/全半角/大小写及公斤/斤的唯一确定性匹配,仍无唯一结果才调用 AI。第二趟只会收到服务端已固化的精确 PDD 原始标签;Agent 只在已打开的规格面板内做有限纵向滑动,每次重新读取节点并按完整规范化文字精确点击,连续没有新证据或达到上限即停止。尺码的任务目标与页面值在选择边界使用同一安全尾价规范化;不改写任务快照,规范化为空、仍含货币符号或多个原始候选折叠为同一值时安全失败。

任务 payload 新增必传布尔字段 specResolutionAllowed,它是 Android 是否可以把一次 PURCHASE_SPEC_TARGET_NOT_VISIBLE 转为 spec_probe_completed 的唯一资格事实。服务端仅在 taskType=syb_order、规则声明 purchase.spec-probe.v1、没有固化 SpecDecisionRequestID 且规格来源允许既有慢路径时返回 true;stock、direct_select、已固化规格决策、能力缺失及其他组合均返回 false。普通就地重试保留 SpecDecisionRequestID、目标规格、映射规格和规格决策快照,不能恢复探测资格。Android 不得根据映射是否非空、执行 phase、错误文字或本地判断扩大资格。

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 只允许管理员或采购员人工解除,永不自动重派。

openSpecPanel.textAliases 是可选的候选过滤条件,不是原始页面文本选择器。省略该字段时,Agent 使用语义安全的规格入口或底部购买入口;提供时也只能与这些安全候选取交集,匹配不到即返回 PURCHASE_SPEC_ENTRY_NOT_FOUND。

底部购买入口同时保留语义文字锚点和已经验证的最近可点击卡片。评价文字出现在锚点、卡片、祖先或后代上下文时拒绝候选;fresh 点击必须绑定该卡片,卡片由可点击变为不可点击时按节点失效处理,不得继续向更宽父容器漂移。点击后只有识别到规格面板才算成功;若进入具有顶部评价标题及至少两项评价页信号的评价列表,Agent 最多执行一次系统返回,确认回到 PDD 商品页后以 PURCHASE_SPEC_ENTRY_OPENED_REVIEW / returned_to_product 失败并停止,不得自动再次点击同一入口。返回失败或无法确认分别使用稳定子原因 back_failed / return_unconfirmed。入口诊断不保存文字、坐标、控件树或截图。

Android #42/#36 使用本地 SQLite 保存恢复与重传所需的任务、attempt 和 Outbox;最终结果和 Outbox 在同一事务写入。演练执行器仍在安全点停止。Agent 0.2.0 另外声明 purchase.live.v1、purchase.address-update.v1 和 purchase.order-create.v1,仅在 live 规则同时包含受限 updateShippingAddress、createOrder、readOrderResult 动作时启用。

正式地址动作在已经选好规格、数量的当前规格/下单面板内执行,不额外点击进入确认页。若脱敏手机号地址入口被裁切,Agent 只在唯一、可见且占据主要屏幕宽度的纵向面板容器内执行有限向下拉动,每次重新读取页面;不得退化为选择页面最大滚动区域。地址入口通过唯一脱敏手机号文字节点确认并在每次动作前重新定位;即使文字节点不可点击,也只允许在该文字节点中心执行一次精确手势,禁止向上查找并点击可能覆盖整个下单面板的宽泛可点击祖先。近乎完全重叠无障碍重复节点合并为一个目标,多个独立目标、多个候选面板、滚动无进展或页面切换超时均以分阶段错误结束并禁止创建订单。入口出现后,Agent 精确选择唯一修改按钮和详细地址输入框;“修改”“保存”和“提交订单”等文字节点即使要沿父链执行点击,也始终保留原始文字、类名和位置作为新鲜定位锚点,可点击父节点只用于验证和最终动作,禁止用无文字父节点替换锚点。按首个 - 或 _ 截断旧后缀,追加任务 addressSuffix,保存并回读完整新地址。地址全文只在本次执行内存中存在,不进入本地最终确认快照、日志或服务端。

地址编辑页存在多个输入框时,以“详细地址”标签为结构锚点,只接受与标签纵向相交且位于标签右侧的唯一、可见、启用、非空输入框;不能要求页面只有一个输入框,也不能按输入框顺序猜测。

创建订单前先在本地事务保存 order_submit_started、不可逆时间、稳定的 orderSubmitRequestId 和脱敏最终确认快照,再调用服务端同名接口;只有两侧标记完成并重新校验商品、规格、数量、单价、地址后缀、PDD 包/Activity 与唯一创建订单按钮后,才点击一次。重启时重放同一标记请求并只读核单;无法取得唯一未付款订单号和 PDD 下单时间时提交 order_result_unknown。支付文字仅用于识别未付款/离开支付页,永不点击。服务端数据库仍是最终事实来源;双方均不保存原始控件树、截图、PDD 凭据或完整收货地址。

创建订单后若出现 Android 多微信应用选择器,只读核单器必须同时确认前台包为 android / com.android.intentresolver、Activity 为白名单 ChooserActivity / ResolverActivity、页面出现已知系统选择器标题且至少一个候选以“微信”开头,才允许执行一次系统返回;不得点击任何微信候选。若前台已经是精确微信包 com.tencent.mm,只读核单器不得点击、输入、登录、支付或强制停止微信,只允许执行一次无参数 PDD 启动 Intent 并等待既有 PDD 任务栈回到前台;startActivity() 成功只表示恢复请求已发起。请求后在固定最多 15 次、每次 200ms 的宽限期内允许微信或空窗口短暂残留,不执行点击、返回、输入或滑动;观察到 PDD 后结束宽限,宽限超时、已经观察到 PDD 后再次进入微信或出现稳定未知应用时返回未知结果。随后若前台为 PDD com.xunmeng.pinduoduo.app_pay.core.PayActivity 或当前页面出现支付动作文字,最多再返回一次。返回后只读解析唯一订单号和下单时间;选择器或支付页重复出现、白名单不成立、恢复动作重复、无法到达订单详情、结果不唯一或超时均返回未知结果,禁止再次点击创建订单、取消订单或支付。 order_result_unknown 保持订单号和下单时间为空,但允许携带创建订单前已经严格验证的 actualUnitPriceCent。Agent 同时提交脱敏稳定失败阶段,服务端只接受白名单并按错误码写入固定提示,不信任或保存页面原文;阶段覆盖空窗口超时、选择器返回失败、微信恢复失败/超时、未知应用、支付页返回失败/重复、订单上下文缺失、订单号缺失/歧义、下单时间缺失/无效和待付款证据缺失。任务与 attempt 保存同一错误阶段,Admin 和 Agent 历史读取数据库最终事实;旧 Agent 未提交阶段时归一为 PURCHASE_ORDER_RESULT_UNKNOWN。

错误码 普通提示
PURCHASE_MODE_NOT_ALLOWED 当前任务模式不允许执行此操作
PURCHASE_RULE_INVALID 采购规则不可用,请联系管理员
AGENT_CAPABILITY_MISMATCH 当前手机版本不支持这个任务
PURCHASE_SPEC_NOT_MATCHED 没有找到可用的商品规格
PURCHASE_SPEC_TARGET_NOT_VISIBLE 有界搜索后没有看到完整精确规格
PURCHASE_SPEC_TARGET_AMBIGUOUS 同一维度存在多个严格等价候选
PURCHASE_SPEC_SAFE_TARGET_MISSING 目标可见但无法确定安全且唯一的点击目标
PURCHASE_SPEC_CLICK_FAILED 精确目标点击失败;errorMessage 为稳定子原因
PURCHASE_SPEC_SELECTION_UNCONFIRMED 点击成功但无法确认精确选中状态
PURCHASE_SPEC_REPROBE_REJECTED 规格探测资格已使用,重复探测被拒绝
PURCHASE_SPEC_ENTRY_NOT_FOUND 没有找到安全且唯一的商品规格入口
PURCHASE_SPEC_ENTRY_TARGET_AMBIGUOUS 规格入口候选不唯一
PURCHASE_SPEC_ENTRY_CLICK_FAILED 规格入口 fresh 点击失败;errorMessage 为稳定子原因
PURCHASE_SPEC_ENTRY_OPENED_REVIEW 规格入口误入评价页;errorMessage 记录一次安全返回结果
PURCHASE_SPEC_PANEL_NOT_OPENED 点击后未识别到商品规格面板
PURCHASE_PRICE_OUT_OF_RANGE 当前商品单价超出允许范围
PURCHASE_ADDRESS_UPDATE_FAILED 收货地址修改失败,未创建订单
PURCHASE_ORDER_RESULT_UNKNOWN 无法确认订单是否创建,请人工检查
PURCHASE_PAYMENT_FORBIDDEN 系统禁止自动付款

Agent 当前设备任务历史(#90)

四个只读接口统一使用设备注册所得的 Device Token,只返回该 Token 对应设备最近 30 天内的任务;设备 A 查询设备 B 的任务时按不存在处理,不泄露任务是否存在。

GET /api/agent/v1/collection-tasks?page=1&pageSize=20&status=failed&taskNo=%2335
GET /api/agent/v1/collection-tasks/{taskId}
GET /api/agent/v1/purchase-tasks?page=1&pageSize=20&status=failed&taskNo=CG-12
GET /api/agent/v1/purchase-tasks/{taskId}
  • pageSize 最大为 50;status 与 taskNo 可以组合过滤。
  • 采集任务编号允许 35 或 #35,采购任务编号允许 12 或不区分大小写的 CG-12;服务端按精确编号匹配。
  • 列表和详情不返回 Device Token、URL、收货地址、规则快照或原始控件树。采购项额外返回服务端计算的 retryable 和可选 retryDisabledReason;除受控失败重试外,不提供取消、修改既有订单或支付入口。
  • 采集摘要返回当前 attemptNumber;采集详情返回标题、店铺、销量、评价数、规格维度、颜色价格、SKU、缺失项、结构化错误,以及不含规则快照的历史 attempt 序号、状态、规则 ID、错误和时间摘要。详情另返回 colorImages 数组,元素为 {color,imagePath,width,height},只包含 source_task_id 等于当前任务的颜色图片并按颜色排序;无图时必须为 []。imagePath 是既有 /static/uploadfile/goauto-color/ 相对路径,Agent 使用已配置服务端 Origin 加载,加载失败不得影响其他详情字段。
  • 采购列表和详情分别返回任务目标规格 targetColor / targetSize 与最终执行规格 mappedColor / mappedSize。Agent 界面必须分开展示;自动化只执行服务端下发的 mapped* 精确规格,映射为空时仍可查看原始目标,不得以目标值替代执行值。详情另返回蝦皮订单号、PDD 商品、数量、实际单价、PDD 订单号、下单时间和结构化错误。
  • Agent 提交采购结果时可携带 actualUnitPriceCent(人民币分,非负)。服务端只保存 Agent 实际观察到的值;历史任务或未观察到价格的结果保持 null,客户端显示“未记录”。

Agent 受控重新采集(#91)

POST /api/agent/v1/collection-tasks/{taskId}/reset
Authorization: Bearer <device-token>
Content-Type: application/json

{"requestId":"<uuid>"}
  • 只允许任务原关联设备使用自身 Device Token 重采:所有来源的 failed 任务均可重采;Admin 来源继续允许 completed、completed_partial 重置,Agent 当前页来源只允许失败后重采。跨设备统一返回 TASK_NOT_FOUND。
  • requestId 必须为 UUID。同一请求重复提交不会重复归档、递增 attempt 或清空结果,响应中的 replayed 为 true。
  • 服务端在事务内锁定设备和任务,重新校验设备在线、同商品无其他 pending/running 采集任务,并确认该设备没有正在执行或持有有效租约的其他采集/采购任务。
  • 成功时复用原 collection_task.id,把旧终态执行归档到 collection_task_attempt,将 attemptNumber 加一,再清理主表当前结果/错误/租约并恢复为 pending;不会新建采集任务。
  • 新 attempt 使用最新有效规则:Admin 来源读取原 ruleId 的当前存活内容,Agent 当前页来源读取当前手动采集默认规则。规则不存在、不可用或与设备能力不兼容时拒绝且不修改任务。
  • 当前页任务尚未识别商品时允许下一 attempt 首次绑定;已有商品身份时重新识别必须逐字命中原 goods_id,不同商品返回身份冲突。
  • 响应返回 taskId、attemptNumber、status 和可选的 replayed,不返回规则快照、URL、Token、控件树或截图。
  • 设备离线、任务非终态、设备忙、规则不可用或同商品存在活动任务时返回明确冲突,不支持离线排队。

Agent 受控采购重试(#95、#157)

普通失败任务的“重试采购”改为就地重跑:

POST /api/agent/v1/purchase-tasks/{taskId}/reset
Authorization: Bearer <device-token>
Content-Type: application/json

{"requestId":"<uuid>"}

成功响应:

{
  "data": {
    "taskId": 12,
    "taskNo": "CG-12",
    "attemptNumber": 2,
    "status": "pending",
    "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 商品已有更新任务时,服务端明确拒绝且不得部分修改任务。

既有新建任务接口保留原语义:

POST /api/agent/v1/purchase-tasks/{taskId}/retry
Authorization: Bearer <device-token>
Content-Type: application/json

{"requestId":"<uuid>"}
  • /retry 的 AgentRetry → BatchRetry → Create 行为不变:保留来源失败任务并创建新任务,返回 sourceTaskId、sourceTaskNo、新 taskId、新 taskNo 和 replayed。
  • Android 普通“重试采购”不再调用 /retry;只有 #132 替代商品规格匹配完成后的“继续采购”继续调用它。Admin 批量重试行为也不变。
  • “继续采购”会重新解析替代商品、规格映射与价格并生成新地址后缀;这与普通失败任务保持快照的就地重跑不可互换。
  • 两个入口都不执行支付。真机调用可能创建待付款订单,必须先取得人工授权。

Agent 任务记录范围与同步(#99)

采集、采购任务列表接口新增可选查询参数 days:

GET /api/agent/v1/collection-tasks?page=1&pageSize=20&days=7&status=failed&taskNo=%2335
GET /api/agent/v1/purchase-tasks?page=1&pageSize=20&days=7&status=failed&taskNo=CG-12
  • days 只允许 1–30 的正整数;省略时为 30,保持旧客户端兼容。详情接口仍限制最近 30 天。
  • 记录范围、状态、任务编号和分页只作用于当前 Device Token 对应设备,不能查询其他设备。
  • Agent 采集/采购 Tab 的顶部下拉只重新读取当前 Tab、当前筛选、当前编号和当前页;不领取、执行、重置或重试任务。
  • 设置 Tab 可输入最近 1~30 天的任意整数,默认 7 天;空值、非整数或越界时不发起请求并提示“请输入 1~30 天”。一次同步当前设备的采集与采购列表摘要;同步为服务端到 Agent 的单向读取,允许分别成功并显示部分同步结果。
  • 本地只缓存列表摘要和同步时间;详情仍按需请求。缓存不得包含 Device Token、完整规则快照、PDD URL、地址、控件树或截图。同步本身不打开 PDD、不修改地址、不创建订单、不支付。

Agent 当前页面临时采集(#101)

管理端默认规则

GET /api/admin/v1/collection-rules/agent-manual-setting
PUT /api/admin/v1/collection-rules/agent-manual-setting
Content-Type: application/json

{"requestId":"<uuid>","ruleId":3}
  • 只允许管理员读取和修改“Agent 手动采集默认规则”;PUT 的 requestId 必须为 UUID,重复请求幂等。
  • 规则必须是仍存在的 PDD 商品详情 v2 规则。数据库迁移首次执行时,优先选择最近成功/部分成功任务使用的存活规则;没有成功历史时选择最近更新的存活 v2 规则;仍没有时保持未配置。
  • 创建任务时重新验证规则与设备能力,并把完整规则快照固化到第 1 次 attempt;以后修改默认规则不影响正在执行的 attempt,但失败任务人工重采时会读取当前默认规则并固化到新的 attempt。

创建并占用当前设备

POST /api/agent/v1/current-page-collection-tasks
Authorization: Bearer <device-token>
Content-Type: application/json

{"requestId":"<uuid>"}
  • Device Token 确定设备。服务端在事务中锁定设备,校验在线、空闲、无活动采集/采购任务、默认规则和 collector.pdd.current-page-share.v1 能力。
  • 成功即创建来源为 agent_current_page 的标准采集任务,固定当前设备、状态为 running、建立租约并返回规则快照;不再调用普通 next → claim → start。
  • 同一设备与 requestId 重放返回原任务且 replayed=true,不创建第二条任务、不续租。
  • 身份识别前响应中的 pddProductId 为 null,urlSnapshot 与 goodsIdSnapshot 为空字符串;普通 admin 来源任务仍必须在创建时具备完整商品身份。

识别当前商品

POST /api/agent/v1/current-page-collection-tasks/{taskId}/identify
Authorization: Bearer <device-token>
Content-Type: application/json

{"requestId":"<uuid>","shareUrl":"https://p.pinduoduo.com/..."}
  • Android 只上传从本次新鲜剪贴板内容中唯一提取出的分享 URL,不上传完整分享文案。
  • Agent 从剪贴板文本提取唯一白名单链接:含 goods_id 的链接直接提交;白名单内但 URL 本身无 goods_id 的链接(包括 p.pinduoduo.com 短链和 mobile.yangkeduo.com/goods2.html?ps=...)优先在手机侧用无 Cookie、无项目凭据的移动端 GET 展开,最多跟随 4 次跳转、每跳校验 HTTPS/主机/端口/userinfo,连接与读取各超时 5 秒,正文最多读取 64KB。URL 或正文只接受边界明确的 goods_id,不得把 refer_goods_id 等相近字段当作商品身份。展开失败可提交原链接,由服务端以相同白名单、最多 4 次重定向和 10 秒总超时兜底;请求字段仍只传 shareUrl。
  • 服务端提取 5~32 位纯数字 goods_id,形成标准 URL,并在事务中创建或复用唯一 PDD 商品;同商品已有活动采集任务或身份冲突时拒绝。
  • 相同识别 requestId 直接返回已确认身份且不再次访问短链;任务首次确认身份后,不允许不同请求覆盖为其他商品。
  • 识别后继续复用 POST /api/agent/v1/tasks/{taskId}/result 与 /fail。结果接口要求任务已绑定身份且结果 goods_id 一致;完成、部分完成和失败继续按既有状态机释放设备槽。
  • Agent 历史的任务摘要返回 source 和当前 attemptNumber;详情返回不含规则快照的历史 attempt 摘要。所有 failed 当前页任务均可通过既有 reset 接口复用原任务重采;发起前 Android 必须确认无障碍就绪、没有本地执行中任务、最近见过 PDD 前台并通过采集冷却。

失败任务采集替代商品(#130)

失败采集详情和失败采购详情增加以下服务端计算字段:

{
  "replacementEligible": true,
  "replacementDisabledReason": null,
  "replacementMappingStatus": "matching",
  "replacementActivationStatus": "activated",
  "replacementActivationErrorMessage": null
}
  • 采集来源的 replacementEligible=true 不再要求任务为 failed,也不检查错误码;当前 Device Token 对应设备、源商品存在、没有生效或待处理替换,且该商品名下不存在 running、order_submit_started、order_result_unknown 采购任务时即可返回。命中采购硬拦截时 replacementDisabledReason 明确说明正在执行或结果待核对。采购来源仍要求失败任务且错误码逐字等于 PDD_LINK_INVALID 或 PDD_GOODS_SOLD_OUT;其他设备、无源商品或进行中的替换均不得由 Android 自行放宽。
  • replacementMappingStatus 取当前来源对应的分项状态:matching、matched 或 manual_required。采购来源必须限定到该任务的 shopeeProductId;不能以替换主表总体状态代替。
  • replacementActivationStatus 为 pending、activated 或 failed;激活失败时已采集数据仍为完成态,并返回限长的 replacementActivationErrorMessage。

创建替代商品采集任务仍调用:

POST /api/agent/v1/current-page-collection-tasks
Authorization: Bearer <device-token>
Content-Type: application/json

{
  "requestId": "<uuid>",
  "replacementOrigin": {"type": "collection", "taskId": 123}
}

replacementOrigin.type 只能是 collection 或 purchase。服务端在创建事务内重新检查资格,并把来源、可选纠错记录和初始 pending 激活状态固化到任务;同一 requestId 重放时来源必须完全一致。资格变化返回 HTTP 409 和 REPLACEMENT_ORIGIN_NOT_ELIGIBLE。

识别与结果提交继续复用当前页面采集接口。结果事务提交后,服务端调用 #131 的登记/纠错并生效流程:成功返回正常完成详情;生效失败返回 HTTP 409、REPLACEMENT_ACTIVATION_FAILED,但采集任务、商品、规格和 SKU 已安全保存。服务启动恢复只重试 pending / failed 的激活步骤,不重新打开 PDD 或重新采集。

替换匹配完成后继续采购(#132)

采购任务详情在 #130 字段之外增加:

{
  "replacementMappingStatus": "matched",
  "continuePurchaseEligible": true,
  "continuePurchaseDisabledReason": null
}
  • continuePurchaseEligible 是 Agent 是否显示“继续采购”的唯一资格事实;Android 不组合任务状态、替换状态或既有 retryable 自行推断。
  • 服务端仅在该采购任务对应虾皮商品的 replacement item 为 matched 时计算继续资格,不读取替换主表总体状态。资格包含既有失败重试的终态、最新任务、不可逆边界、档案、持久化映射、PDD 候选、价格、设备与能力门禁。
  • 详情资格检查不调用 AI,也不把重新推导出的候选当成已确认映射。资格不通过时返回普通人可理解的 continuePurchaseDisabledReason;matching / manual_required 继续使用对应状态文字且不显示按钮。
  • 点击仍调用既有 POST /api/agent/v1/purchase-tasks/{taskId}/retry,请求体仍为 {"requestId":"<uuid>"}。AgentRetry 在委托 BatchRetry 前重新校验 replacement item 与继续资格,随后由 BatchRetry/Create 执行最终档案和并发校验;没有新增采购任务创建路径。
  • Admin 的 BatchRetry 接口和行为不变。相同 requestId 重放返回同一新任务;不同 requestId 再点由最新任务门禁拒绝。

采集颜色图片上传(#133)

结构化采集结果成功提交后,Agent 可为任务已采集的颜色逐张调用:

POST /api/agent/v1/tasks/{taskId}/color-images
Authorization: Bearer <device-token>
Content-Type: multipart/form-data

color=<任务结果中的精确颜色值>
file=<JPEG 二进制>

成功返回 HTTP 201:

{
  "data": {
    "color": "紫色",
    "imagePath": "/static/uploadfile/goauto-color/<uuid>.jpg",
    "width": 316,
    "height": 316
  }
}

契约约束:

  • 必须先成功调用 POST /api/agent/v1/tasks/{taskId}/result;任务须为当前 Device Token 所属设备的 completed 或 completed_partial 任务。
  • color 必须逐字匹配该任务持久化的 color 维度值;服务端不做相近颜色推断。
  • 只接受实际内容为 JPEG 的文件;单张最大 512 KiB,宽高必须为正且均不超过 1024;单任务最多 64 张。
  • 同一 PDD 商品和颜色重复上传以最新图片为准。响应路径供 Admin 商品详情展示,不返回整屏截图、控件树、XML 或个人数据。
  • 接口独立于结果提交。任一上传失败都不得修改任务状态、missing、规格、颜色价格或 SKU;Agent 不进行会拖长任务的重试。
  • 典型错误:COLOR_IMAGE_TOO_LARGE(HTTP 413)、COLOR_IMAGE_UNSUPPORTED(HTTP 415)、COLOR_IMAGE_INVALID(HTTP 422)、任务/设备/状态冲突(HTTP 409)。

采购规则管理接口

方法 路径 说明
GET / POST /api/admin/v1/purchase-rules 管理员分页查看或创建严格校验的采购规则
PATCH / DELETE /api/admin/v1/purchase-rules/{ruleId} 管理员修改或删除非当前规则;当前规则不能删除
GET / PUT /api/admin/v1/purchase-rules/current 查看或按 requestId + ruleId 幂等切换当前规则

新采购任务、批量预检、批量创建、批量重试和安全的同任务重试读取当前规则;当前设置缺失、规则已删除或内容不通过正式采购契约时返回明确错误,不回退到内置常量。既有任务的 ruleSnapshot 不变。所有接口仅管理员可用。 priceGuard 支持以下两种互斥形态:

{"enabled":true,"minRatio":0.2,"maxRatio":1.5}
{"enabled":false,"absoluteMaxUnitPriceCent":5000}
  • enabled 缺省时视为 true,兼容既有倍率规则;整个 priceGuard 缺省时继续使用默认倍率 0.2~1.5。
  • 启用时必须提供 minRatio(0.1~1.0)与 maxRatio(1.0~3.0),最多 4 位小数且最低不得大于最高;不得提供绝对上限。
  • 关闭时不得提供倍率字段,必须提供整数分 absoluteMaxUnitPriceCent,范围 1~100000。服务端仍从精确映射颜色读取参考价用于预检和审计,但新任务固化 minUnitPriceCent=0、maxUnitPriceCent=absoluteMaxUnitPriceCent。
  • Android 严格解析两种形态,但不根据规则自行重算价格范围,只执行任务下发的最小/最大单价;未知字段、混用字段、缺少绝对上限或越界值均以 PURCHASE_RULE_INVALID 拒绝。
  • PURCHASE_PRICE_OUT_OF_RANGE 失败结果应携带本次观察到的 actualUnitPriceCent;服务端保存该值用于任务历史诊断。该字段不授权 Agent 放宽任务边界。

Agent 应用版本接口

方法 路径 认证与说明
GET / POST /api/admin/v1/agent-app-releases 仅管理员;列表或上传不超过 200 MB 的 APK,服务端解析版本并计算 SHA-256
PUT /api/admin/v1/agent-app-releases/current 仅管理员;按 requestId + releaseId 幂等设置当前版本
GET /api/admin/v1/agent-app-releases/{releaseId}/download Admin JWT 鉴权下载,不提供公开静态地址
GET /api/agent/v1/app/latest Device Token;返回当前版本元数据或 data=null
GET /api/agent/v1/app/releases/{releaseId}/download Device Token;私有 APK 下载

Agent 只比较整数 versionCode。设备有活动任务时禁止检查、下载和安装;下载到应用私有缓存并校验响应大小与 SHA-256,失败立即删除。安装使用 FileProvider 和 Android 系统安装确认页;未知来源权限必须由用户在系统设置授权,不静默安装。

Admin 蝦皮规格自动匹配批次(#195)

以下接口仅管理员可用,采购员与其他角色必须拒绝;两者不属于 Android Agent 接口,不改变任何 Agent 契约。

POST /api/admin/v1/shopee-spec-auto-match/runs

请求:{ "requestId": "UUID" }。服务端以 requestId 幂等,异步受理默认最多 20 个商品的手动批次,并返回 HTTP 202、data.run 运行摘要。若已有活动批次,不再创建第二个批次,返回当前运行并标记 alreadyRunning=true;相同请求重放标记 replayed=true。

GET /api/admin/v1/shopee-spec-auto-match/runs/latest

返回 data.run;从未执行时为 null。运行摘要包含 id/requestId/trigger/status/batchLimit/scannedCount/eligibleCount/processedCount/confirmedCount/unmatchedCount/failedCount/startedAt/finishedAt 和可选脱敏 errorSummary,不得返回 API Key、Provider 原始响应或商品原始 JSON。

status 当前为 running、completed、completed_partial 或 failed。页面只轮询最近摘要;查询本身不调用 AI。批次只更新蝦皮商品规格映射,不创建采购任务、订单或付款动作。

SYB 异常规格 AI 解析字段(#198)

既有 SYB 商品列表与详情响应增加以下只读字段:aiConfirmed、可空 aiConfidence、可空 aiReason、可空 aiConfirmedAt。aiInputFingerprint 仅用于服务端并发和漂移校验,禁止通过 API 返回。parseStatus / parseNote 继续表示确定性解析器结果,manuallyConfirmed 继续只表示人工确认。

采购预检把仍命中关联蝦皮当前候选的 aiConfirmed=true 视为可信目标规格;候选消失、关联变化或来源重导入使其失效,并返回重新解析/人工修正原因。该能力不新增 Android 接口或 Agent 字段,不创建采购任务或订单。