当前页面采集:剪贴板中转污染 Activity 证据导致详情页误判 #110

Closed
opened 2026-08-27 15:53:50 +08:00 by ila · 5 comments
Owner

所属与来源

  • 关联工单:#101 当前页面采集、#106 分享点击与诊断、#107 PDD 前台恢复、#108/#109 分享链接展开。
  • 来源:用户于 2026-08-27 在 PKG110、Agent 0.9.6(提交 daf7406)真机验证 #109 时反馈:Agent 已能取得 goods_id,画面仍停留在 PDD 商品详情页,但约 30 秒后任务失败并返回 Agent,错误为 PDD_DETAIL_ENTRY_FAILED:未进入 PDD 商品详情页。Codex 随后按用户要求完成只读诊断。
  • 类型:Android Agent / 当前页面采集 / 剪贴板中转后的 Activity 证据污染缺陷。
  • 设计证据:不新增或调整页面、组件、导航和文案,不修改接口、数据库或规则格式;属于恢复 #101 已确认流程的非 UI 缺陷,无需原型。

当前事实与根因(提交 daf7406 复核)

真机及本地数据库证据:

  • 最近三次当前页面任务(#76、#77、#78)均已完成商品身份识别,之后以 PDD_DETAIL_ENTRY_FAILED 失败。
  • 最新任务从 identity_resolved_at 到 finished_at 约 30116ms,与当前规则 collector.timeoutsMs.page=30000 一致,说明失败来自采集器等待页面强证据超时,而非短链或 goods_id 解析。
  • 失败后 Android 任务记录中的 PDD 页面为 com.xunmeng.pinduoduo/.activity.NewPageActivity。
  • 当前 Agent 手动采集规则的页面证据为:
    • package:com.xunmeng.pinduoduo
    • activity:com.xunmeng.pinduoduo.activity.NewPageActivity
    • selector:android:id/content + android.widget.FrameLayout
  • 同一页面在身份识别阶段已经通过相同强证据,并成功找到分享/复制入口,因此规则本身和初始 PDD 页面并非不匹配。

代码根因:

  1. CurrentPageIdentityRunner 点击复制后调用 ClipboardRelayActivity.readFresh()。该透明 Activity 使用独立 taskAffinity 读取新鲜剪贴板,完成后 finishAndRemoveTask(),视觉上重新露出原 PDD 页面。
  2. GoAutoAccessibilityService 的 ActivityEvidenceTracker 只保存一个全局 activityName。收到透明中转页的窗口事件时,原 PDD NewPageActivity 会被 Agent 的 ClipboardRelayActivity 覆盖。
  3. 已确认的代码事实是:ClipboardRelayActivity.readFresh() 返回后,runner 可立即进入下一次 capture(),而中转 Activity 结束与 PDD 窗口恢复事件的处理存在时序竞争。待确认的 ROM 假设是:ColorOS 在透明中转页移除并重新露出原 PDD 窗口时可能不再补发 PDD TYPE_WINDOW_STATE_CHANGED。修复不得依赖该假设成立;无论事件迟到还是不补发,都必须保持同包 Activity 证据一致。
  4. PddScreenParser.pageEvidenceMatched 要求当前 root 包名、记录的 Activity 和节点选择器三项同时匹配。上述“PDD root + Agent Activity”组合持续无法匹配,最终在 30 秒页面超时后报 PDD_DETAIL_ENTRY_FAILED。
  5. 任务结束后返回 Agent 是既有失败收尾行为,不是失败原因。

现有单元测试只覆盖同一包内 PopupWindow 不得覆盖已确认 Activity,没有覆盖“PDD Activity → 跨包 Agent 透明 Activity → 无新的 PDD 窗口事件但 PDD root 已恢复”的真实序列。

目标

  1. Activity 证据按 package 隔离保存,Agent 的剪贴板中转 Activity 不得覆盖最后一次已确认的 PDD Activity。
  2. UiSnapshot 根据当前 rootInActiveWindow.packageName 读取该包对应的 Activity 证据。
  3. 剪贴板中转结束并重新露出原 PDD 页面后,采集器立即通过原规则页面强证据并继续采集,不等待 30 秒失败。
  4. 保持精确 Activity 门禁,不把页面判断降级为只看包名或控件。

非目标

  • 不修改短链展开、goods_id 提取、服务端身份裁决或 #108/#109 白名单逻辑。
  • 不修改当前页面任务创建/识别时序、状态机、租约、数据库和共享 API。
  • 不放宽或改写现有采集规则,不修改默认规则设置。
  • 不改分享/复制目标选择、剪贴板新鲜度策略、PDD 前台恢复和任务结束返回机制。
  • 不读取、保存或输出原始控件树、截图、剪贴板正文、链接或 goods_id。
  • 不涉及 Admin/Web、采购、地址、创建订单或支付。

实施方案

Activity 证据隔离与原子快照

  1. ActivityEvidenceTracker 改为按规范化 packageName 保存最后一次由 PackageManager 确认的 Activity;内部容器必须支持无障碍事件线程写入与采集任务线程读取,使用 ConcurrentHashMap、同步保护或等效的线程安全不可变快照。
  2. 只修改 tracker 的查询接口为按包查询(如 current(packageName));UiDriver.currentActivity() 的无参签名保持不变,避免波及 RuleExecutor、采购路径及 stub。
  3. GoAutoAccessibilityService.currentActivity() 的语义调整为:读取当前 rootInActiveWindow 的 packageName,再查询该包对应的 Activity;包名为空或该包从未观察到已声明 Activity 时返回 null,禁止回退到其他包。
  4. capture() 必须用同一个局部 root 原子组装 UiSnapshot:主路径先固定 rootPackage,再以 activityTracker.current(rootPackage) 取得 Activity 并遍历该 root;不得在组装期间再次读取 rootInActiveWindow。当 root 为 null 时直接返回 UiSnapshot(null, null, emptyList()),不得拼接另一次读取到的包名或 Activity。
  5. 保留相对 Activity 名规范化与“PopupWindow/Dialog 等未被 PackageManager 确认为 Activity 的窗口不能覆盖同包证据”的行为。任何查询都只能返回传入包的证据,不允许跨包 fallback。
  6. 按包证据可能陈旧,仍必须与当前 root 包名、规则精确 Activity 和非空节点选择器共同校验。当前规则节点较通用,本工单不改规则,但不得宣称三项校验已完全消除 stale 风险;通过负向测试约束跨包、未知包和同包新 Activity 的行为,不增加会导致长任务中途失效的简单 TTL。

详情入口诊断

  1. 在 PddProductDetailCollector 页面证据等待阶段增加可选、默认 no-op 的安全诊断回调;当前页面采集路径传入现有 SafeAgentDiagnosticRecorder,普通管理端采集和采购路径行为保持不变。
  2. 复用现有 AgentDiagnosticStore 表结构和字段,不新增 SQLite 版本、服务端字段或共享接口。新增 DETAIL_ENTRY 阶段和必要原因枚举,超时至少区分 ROOT_UNAVAILABLE、PACKAGE_MISMATCH、ACTIVITY_MISMATCH、SELECTOR_MISMATCH;记录仅限 packageMatched、activityMatched、选择器匹配数量、尝试次数和耗时。
  3. 页面首次匹配时记录 DETAIL_ENTRY_MATCHED(或等价成功原因),用于真机量化“采集器启动到首次页面证据匹配”的耗时。诊断不得包含包外敏感数据、控件文本/树、截图、链接、goods_id 或剪贴板内容。

衔接与回归

  1. 增加 ActivityEvidenceTrackerTest:
    • PDD NewPageActivity → Agent ClipboardRelayActivity 后,查询 PDD 仍返回 NewPageActivity,查询 Agent 返回 ClipboardRelayActivity;
    • 未观察过的包返回 null;
    • 同包 PopupWindow/Dialog 不覆盖已确认 Activity;
    • PDD 出现另一个由 PackageManager 确认的 Activity 后必须替换旧证据;
    • 相对 Activity 名继续规范化。
  2. 增加组件级衔接测试:用 tracker 在跨包中转序列后返回的 PDD Activity 构造恢复后的 UiSnapshot,交由页面解析/采集入口验证强证据匹配;同时覆盖 Agent root、未知包、Activity 不匹配和选择器不匹配均不得通过。
  3. 如真机证明 finishAndRemoveTask() 与 PDD root 恢复之间仍有短暂窗口,只允许等待“PDD root + 同包 Activity + 节点证据”在既有有界轮询内恢复;不得加入固定长等待、放宽页面证据或重新打开 PDD 深链掩盖问题。

安全边界

  • 继续要求 PDD 包名、精确 Activity 和非空节点证据同时匹配。
  • 只保存 Activity 类名这一既有运行时内存证据,不新增持久化、日志字段或上传数据。
  • 不记录原始控件树、截图、链接、goods_id 或剪贴板内容。
  • 不自动搜索/选择相似商品,不调用采购、地址、订单和支付代码。

验收标准

  • 单元测试复现跨包透明 Activity 覆盖场景;修复后 PDD Activity 证据保持为最后一次已确认的 NewPageActivity,并证明 tracker 的并发读写容器是线程安全实现。
  • capture() 的 root 存在与 root 缺失两条路径均只返回同一次 root 读取形成的一致快照;当前 root 属于 PDD 时绝不返回 Agent Activity,未知包无证据时返回 null。
  • 同包 PopupWindow、Dialog 等非 Activity 窗口仍不能覆盖已确认 Activity。
  • PKG110 上从真实 PDD 商品详情页发起当前页面采集,复制链接并取得 goods_id 后无需重开 PDD 页面,继续进入商品详情采集。
  • 不再等待约 30 秒并报 PDD_DETAIL_ENTRY_FAILED;PKG110 上“采集器启动到首次页面证据匹配”小于 3 秒,并以 DETAIL_ENTRY 成功诊断的耗时证据记录;任务最终按真实采集结果进入 completed、completed_partial 或其他具体采集失败码。
  • 普通管理端采集的浏览器导航、PDD 精确 Activity 校验及 #107 前台恢复不回退。
  • DETAIL_ENTRY 超时诊断可区分 root、包名、Activity 和选择器失配,且不新增 SQLite schema/版本;诊断、日志和数据库不新增链接、goods_id、剪贴板、控件文本/树或截图。

验证方式

  • cd android && .\gradlew.bat :app:testDebugUnitTest :app:assembleDebug
  • 受影响的 Activity tracker、原子快照、当前页面身份识别、详情入口诊断和商品详情采集器单元测试;组件测试不强行实例化 Android Service,以 tracker + UiSnapshot + parser/collector 的可控组合覆盖衔接。
  • PKG110 真机:设备空闲、PDD 已登录且停留在已授权测试商品详情页时执行一次“Agent 当前页面采集”,记录 Agent 版本、规则 ID/快照、任务号和最终状态;不记录商品链接或 goods_id。
  • 真机验证前确认无活动采集/采购任务;未覆盖的 ROM、多设备和异常路径如实回写。

依赖、并行与风险

  • 前置依赖:#109 的短链展开实现 daf7406 已完成并安装到 PKG110;本工单是其真机端到端验收发现的后续阻塞缺陷。
  • 不建议与其他修改 GoAutoAccessibilityService、ActivityEvidenceTracker 或当前页面身份流程的工单并行。
  • 风险:按包缓存的 Activity 可能是该包内最后一次已确认页面;当前节点选择器较通用,所以不能声称三项证据完全消除 stale 风险。实施必须保证无跨包 fallback、同包新 Activity 覆盖旧证据,并继续联合当前 root 包名、精确 Activity 和节点证据校验;本工单不以简单 TTL 或放宽规则处理该风险。
  • 回退:还原本工单 Android 提交即可,不涉及数据库、接口和服务端部署。

文档影响

  • 目标行为已由 Architecture/Business Rules/API Contract 规定为“剪贴板中转结束后露出原 PDD 页面,并以包名、精确 Activity、页面证据继续采集”。按包隔离属于内部修复,预计无长期文档影响;但新增 DETAIL_ENTRY 本地诊断后,实施时应检查现有 Wiki 是否枚举诊断阶段。若未枚举则在工单说明无长期文档影响并跳过同步;若已有枚举则按 Wiki-first 门禁更新。

状态

方案已按 2026-08-27 全栈评审结论收敛,待 Codex 实施。

## 所属与来源 - 关联工单:#101 当前页面采集、#106 分享点击与诊断、#107 PDD 前台恢复、#108/#109 分享链接展开。 - 来源:用户于 2026-08-27 在 PKG110、Agent 0.9.6(提交 `daf7406`)真机验证 #109 时反馈:Agent 已能取得 `goods_id`,画面仍停留在 PDD 商品详情页,但约 30 秒后任务失败并返回 Agent,错误为 `PDD_DETAIL_ENTRY_FAILED:未进入 PDD 商品详情页`。Codex 随后按用户要求完成只读诊断。 - 类型:Android Agent / 当前页面采集 / 剪贴板中转后的 Activity 证据污染缺陷。 - 设计证据:不新增或调整页面、组件、导航和文案,不修改接口、数据库或规则格式;属于恢复 #101 已确认流程的非 UI 缺陷,无需原型。 ## 当前事实与根因(提交 daf7406 复核) 真机及本地数据库证据: - 最近三次当前页面任务(#76、#77、#78)均已完成商品身份识别,之后以 `PDD_DETAIL_ENTRY_FAILED` 失败。 - 最新任务从 `identity_resolved_at` 到 `finished_at` 约 30116ms,与当前规则 `collector.timeoutsMs.page=30000` 一致,说明失败来自采集器等待页面强证据超时,而非短链或 goods_id 解析。 - 失败后 Android 任务记录中的 PDD 页面为 `com.xunmeng.pinduoduo/.activity.NewPageActivity`。 - 当前 Agent 手动采集规则的页面证据为: - package:`com.xunmeng.pinduoduo` - activity:`com.xunmeng.pinduoduo.activity.NewPageActivity` - selector:`android:id/content` + `android.widget.FrameLayout` - 同一页面在身份识别阶段已经通过相同强证据,并成功找到分享/复制入口,因此规则本身和初始 PDD 页面并非不匹配。 代码根因: 1. `CurrentPageIdentityRunner` 点击复制后调用 `ClipboardRelayActivity.readFresh()`。该透明 Activity 使用独立 taskAffinity 读取新鲜剪贴板,完成后 `finishAndRemoveTask()`,视觉上重新露出原 PDD 页面。 2. `GoAutoAccessibilityService` 的 `ActivityEvidenceTracker` 只保存一个全局 `activityName`。收到透明中转页的窗口事件时,原 PDD `NewPageActivity` 会被 Agent 的 `ClipboardRelayActivity` 覆盖。 3. 已确认的代码事实是:`ClipboardRelayActivity.readFresh()` 返回后,runner 可立即进入下一次 `capture()`,而中转 Activity 结束与 PDD 窗口恢复事件的处理存在时序竞争。待确认的 ROM 假设是:ColorOS 在透明中转页移除并重新露出原 PDD 窗口时可能不再补发 PDD `TYPE_WINDOW_STATE_CHANGED`。修复不得依赖该假设成立;无论事件迟到还是不补发,都必须保持同包 Activity 证据一致。 4. `PddScreenParser.pageEvidenceMatched` 要求当前 root 包名、记录的 Activity 和节点选择器三项同时匹配。上述“PDD root + Agent Activity”组合持续无法匹配,最终在 30 秒页面超时后报 `PDD_DETAIL_ENTRY_FAILED`。 5. 任务结束后返回 Agent 是既有失败收尾行为,不是失败原因。 现有单元测试只覆盖同一包内 PopupWindow 不得覆盖已确认 Activity,没有覆盖“PDD Activity → 跨包 Agent 透明 Activity → 无新的 PDD 窗口事件但 PDD root 已恢复”的真实序列。 ## 目标 1. Activity 证据按 package 隔离保存,Agent 的剪贴板中转 Activity 不得覆盖最后一次已确认的 PDD Activity。 2. `UiSnapshot` 根据当前 `rootInActiveWindow.packageName` 读取该包对应的 Activity 证据。 3. 剪贴板中转结束并重新露出原 PDD 页面后,采集器立即通过原规则页面强证据并继续采集,不等待 30 秒失败。 4. 保持精确 Activity 门禁,不把页面判断降级为只看包名或控件。 ## 非目标 - 不修改短链展开、goods_id 提取、服务端身份裁决或 #108/#109 白名单逻辑。 - 不修改当前页面任务创建/识别时序、状态机、租约、数据库和共享 API。 - 不放宽或改写现有采集规则,不修改默认规则设置。 - 不改分享/复制目标选择、剪贴板新鲜度策略、PDD 前台恢复和任务结束返回机制。 - 不读取、保存或输出原始控件树、截图、剪贴板正文、链接或 goods_id。 - 不涉及 Admin/Web、采购、地址、创建订单或支付。 ## 实施方案 ### Activity 证据隔离与原子快照 1. `ActivityEvidenceTracker` 改为按规范化 packageName 保存最后一次由 PackageManager 确认的 Activity;内部容器必须支持无障碍事件线程写入与采集任务线程读取,使用 `ConcurrentHashMap`、同步保护或等效的线程安全不可变快照。 2. 只修改 tracker 的查询接口为按包查询(如 `current(packageName)`);`UiDriver.currentActivity()` 的无参签名保持不变,避免波及 `RuleExecutor`、采购路径及 stub。 3. `GoAutoAccessibilityService.currentActivity()` 的语义调整为:读取当前 `rootInActiveWindow` 的 packageName,再查询该包对应的 Activity;包名为空或该包从未观察到已声明 Activity 时返回 null,禁止回退到其他包。 4. `capture()` 必须用同一个局部 `root` 原子组装 `UiSnapshot`:主路径先固定 `rootPackage`,再以 `activityTracker.current(rootPackage)` 取得 Activity 并遍历该 root;不得在组装期间再次读取 `rootInActiveWindow`。当 root 为 null 时直接返回 `UiSnapshot(null, null, emptyList())`,不得拼接另一次读取到的包名或 Activity。 5. 保留相对 Activity 名规范化与“PopupWindow/Dialog 等未被 PackageManager 确认为 Activity 的窗口不能覆盖同包证据”的行为。任何查询都只能返回传入包的证据,不允许跨包 fallback。 6. 按包证据可能陈旧,仍必须与当前 root 包名、规则精确 Activity 和非空节点选择器共同校验。当前规则节点较通用,本工单不改规则,但不得宣称三项校验已完全消除 stale 风险;通过负向测试约束跨包、未知包和同包新 Activity 的行为,不增加会导致长任务中途失效的简单 TTL。 ### 详情入口诊断 7. 在 `PddProductDetailCollector` 页面证据等待阶段增加可选、默认 no-op 的安全诊断回调;当前页面采集路径传入现有 `SafeAgentDiagnosticRecorder`,普通管理端采集和采购路径行为保持不变。 8. 复用现有 `AgentDiagnosticStore` 表结构和字段,不新增 SQLite 版本、服务端字段或共享接口。新增 `DETAIL_ENTRY` 阶段和必要原因枚举,超时至少区分 `ROOT_UNAVAILABLE`、`PACKAGE_MISMATCH`、`ACTIVITY_MISMATCH`、`SELECTOR_MISMATCH`;记录仅限 `packageMatched`、`activityMatched`、选择器匹配数量、尝试次数和耗时。 9. 页面首次匹配时记录 `DETAIL_ENTRY_MATCHED`(或等价成功原因),用于真机量化“采集器启动到首次页面证据匹配”的耗时。诊断不得包含包外敏感数据、控件文本/树、截图、链接、goods_id 或剪贴板内容。 ### 衔接与回归 10. 增加 `ActivityEvidenceTrackerTest`: - PDD `NewPageActivity` → Agent `ClipboardRelayActivity` 后,查询 PDD 仍返回 `NewPageActivity`,查询 Agent 返回 `ClipboardRelayActivity`; - 未观察过的包返回 null; - 同包 PopupWindow/Dialog 不覆盖已确认 Activity; - PDD 出现另一个由 PackageManager 确认的 Activity 后必须替换旧证据; - 相对 Activity 名继续规范化。 11. 增加组件级衔接测试:用 tracker 在跨包中转序列后返回的 PDD Activity 构造恢复后的 `UiSnapshot`,交由页面解析/采集入口验证强证据匹配;同时覆盖 Agent root、未知包、Activity 不匹配和选择器不匹配均不得通过。 12. 如真机证明 `finishAndRemoveTask()` 与 PDD root 恢复之间仍有短暂窗口,只允许等待“PDD root + 同包 Activity + 节点证据”在既有有界轮询内恢复;不得加入固定长等待、放宽页面证据或重新打开 PDD 深链掩盖问题。 ## 安全边界 - 继续要求 PDD 包名、精确 Activity 和非空节点证据同时匹配。 - 只保存 Activity 类名这一既有运行时内存证据,不新增持久化、日志字段或上传数据。 - 不记录原始控件树、截图、链接、goods_id 或剪贴板内容。 - 不自动搜索/选择相似商品,不调用采购、地址、订单和支付代码。 ## 验收标准 - [ ] 单元测试复现跨包透明 Activity 覆盖场景;修复后 PDD Activity 证据保持为最后一次已确认的 `NewPageActivity`,并证明 tracker 的并发读写容器是线程安全实现。 - [ ] `capture()` 的 root 存在与 root 缺失两条路径均只返回同一次 root 读取形成的一致快照;当前 root 属于 PDD 时绝不返回 Agent Activity,未知包无证据时返回 null。 - [ ] 同包 PopupWindow、Dialog 等非 Activity 窗口仍不能覆盖已确认 Activity。 - [ ] PKG110 上从真实 PDD 商品详情页发起当前页面采集,复制链接并取得 goods_id 后无需重开 PDD 页面,继续进入商品详情采集。 - [ ] 不再等待约 30 秒并报 `PDD_DETAIL_ENTRY_FAILED`;PKG110 上“采集器启动到首次页面证据匹配”小于 3 秒,并以 `DETAIL_ENTRY` 成功诊断的耗时证据记录;任务最终按真实采集结果进入 `completed`、`completed_partial` 或其他具体采集失败码。 - [ ] 普通管理端采集的浏览器导航、PDD 精确 Activity 校验及 #107 前台恢复不回退。 - [ ] `DETAIL_ENTRY` 超时诊断可区分 root、包名、Activity 和选择器失配,且不新增 SQLite schema/版本;诊断、日志和数据库不新增链接、goods_id、剪贴板、控件文本/树或截图。 ## 验证方式 - `cd android && .\gradlew.bat :app:testDebugUnitTest :app:assembleDebug` - 受影响的 Activity tracker、原子快照、当前页面身份识别、详情入口诊断和商品详情采集器单元测试;组件测试不强行实例化 Android Service,以 tracker + `UiSnapshot` + parser/collector 的可控组合覆盖衔接。 - PKG110 真机:设备空闲、PDD 已登录且停留在已授权测试商品详情页时执行一次“Agent 当前页面采集”,记录 Agent 版本、规则 ID/快照、任务号和最终状态;不记录商品链接或 goods_id。 - 真机验证前确认无活动采集/采购任务;未覆盖的 ROM、多设备和异常路径如实回写。 ## 依赖、并行与风险 - 前置依赖:#109 的短链展开实现 `daf7406` 已完成并安装到 PKG110;本工单是其真机端到端验收发现的后续阻塞缺陷。 - 不建议与其他修改 `GoAutoAccessibilityService`、`ActivityEvidenceTracker` 或当前页面身份流程的工单并行。 - 风险:按包缓存的 Activity 可能是该包内最后一次已确认页面;当前节点选择器较通用,所以不能声称三项证据完全消除 stale 风险。实施必须保证无跨包 fallback、同包新 Activity 覆盖旧证据,并继续联合当前 root 包名、精确 Activity 和节点证据校验;本工单不以简单 TTL 或放宽规则处理该风险。 - 回退:还原本工单 Android 提交即可,不涉及数据库、接口和服务端部署。 ## 文档影响 - 目标行为已由 Architecture/Business Rules/API Contract 规定为“剪贴板中转结束后露出原 PDD 页面,并以包名、精确 Activity、页面证据继续采集”。按包隔离属于内部修复,预计无长期文档影响;但新增 `DETAIL_ENTRY` 本地诊断后,实施时应检查现有 Wiki 是否枚举诊断阶段。若未枚举则在工单说明无长期文档影响并跳过同步;若已有枚举则按 Wiki-first 门禁更新。 ## 状态 方案已按 2026-08-27 全栈评审结论收敛,待 Codex 实施。
Author
Owner

评审意见(对提交 daf7406 复核)

根因判断与代码一致,「按包隔离 Activity 证据」方向正确,可以实施。以下四点建议在动手前定下来,其中第 1 点会直接减少改动面。

1. 不要修改 currentActivity() 的签名(重要)

方案第 2 条提议改为 current(packageName),第 5 条留待「实施时通过调用路径检查确认影响范围」。该范围已经确定,共 4 处:

  • android/app/src/main/java/cn/ilapage/goauto/agent/automation/GoAutoAccessibilityService.kt:93
  • 同文件 :180(capture() 的 root == null 分支)
  • 同文件 :204(capture() 主路径)
  • android/app/src/main/java/cn/ilapage/goauto/agent/automation/RuleExecutor.kt:53(经 UiDriver 接口)
  • 另有 android/app/src/main/java/cn/ilapage/goauto/agent/automation/PddProductDetailCollector.kt:970 的 stub 实现

UiDriver.currentActivity() 是采购路径同样在用的共享接口,改签名会无谓波及采购代码。

建议改为:ActivityEvidenceTracker 内部按规范化 packageName 保存证据,GoAutoAccessibilityService.currentActivity() 的语义改为「当前 rootInActiveWindow 包名对应的 Activity 证据」,由服务内部取 root 包名后查表。接口签名保持不变,RuleExecutor 与采购侧实现一行都不用改,语义反而更准确。请据此改写方案第 2 条与第 5 条。

2. capture() 的两条返回路径必须一起改

工单只覆盖了 :204 的主路径。:180 的 root == null 分支同样调用 currentActivity(),若不走同一套按包查询,页面过渡期仍会漏出被污染的 Activity 值。请在方案中显式列出这两处。

3. 根因第 3 条应标注为假设,且修复不应依赖它

「ColorOS 在透明中转页移除后可能不再发送 PDD TYPE_WINDOW_STATE_CHANGED 事件」目前没有证据支撑,按 AGENTS.md 的事实边界要求应记为待确认假设,不与代码事实混列。

更重要的是:即使该 ROM 行为正常、事件确实补发,仍存在纯代码层面的时序竞争——ClipboardRelayActivity.readFresh() 返回后 runner 立即 capture(),该事件可能尚未送达。因此「按包隔离」这个修法对该假设不敏感,两种情况都能修好。建议在工单中写明这一点,避免实施时花时间去证伪 ROM 行为。

4. 补充可观测手段

当前 PDD_DETAIL_ENTRY_FAILED 把「包名匹配但 Activity 不匹配」和「节点选择器不匹配」归为同一个错误码,这正是本次只能靠 30 秒超时倒推的原因。建议在页面证据等待超时时,复用 #106 的 AgentDiagnosticStore 记录一条可区分二者的原因枚举。目标 4 坚持精确 Activity 门禁是对的,但门禁需要配套诊断,否则同类问题复发时仍然缺乏证据。

诊断仍不得记录控件树、截图、链接、goods_id 与剪贴板内容,与本工单安全边界一致。

两点小建议

  • 验收标准建议增加一条可量化项:身份识别完成到进入详情采集的间隔小于 3 秒(现有「不再等待约 30 秒」偏定性)。
  • 按包缓存会长期保留每个包最后一次已确认 Activity,存在 stale 风险(工单风险段已提及但未给对策)。实际上「当前 root 包名 + 精确 Activity + 节点强证据三项同时匹配」已足以兜住,建议在风险段写明这一结论,不必额外增加机制。

非目标、安全边界、回退方式各段无需调整。

## 评审意见(对提交 `daf7406` 复核) 根因判断与代码一致,「按包隔离 Activity 证据」方向正确,可以实施。以下四点建议在动手前定下来,其中第 1 点会直接减少改动面。 ### 1. 不要修改 `currentActivity()` 的签名(重要) 方案第 2 条提议改为 `current(packageName)`,第 5 条留待「实施时通过调用路径检查确认影响范围」。该范围已经确定,共 4 处: - `android/app/src/main/java/cn/ilapage/goauto/agent/automation/GoAutoAccessibilityService.kt:93` - 同文件 `:180`(`capture()` 的 `root == null` 分支) - 同文件 `:204`(`capture()` 主路径) - `android/app/src/main/java/cn/ilapage/goauto/agent/automation/RuleExecutor.kt:53`(经 `UiDriver` 接口) - 另有 `android/app/src/main/java/cn/ilapage/goauto/agent/automation/PddProductDetailCollector.kt:970` 的 stub 实现 `UiDriver.currentActivity()` 是采购路径同样在用的共享接口,改签名会无谓波及采购代码。 建议改为:`ActivityEvidenceTracker` 内部按规范化 packageName 保存证据,`GoAutoAccessibilityService.currentActivity()` 的语义改为「当前 `rootInActiveWindow` 包名对应的 Activity 证据」,由服务内部取 root 包名后查表。接口签名保持不变,`RuleExecutor` 与采购侧实现一行都不用改,语义反而更准确。请据此改写方案第 2 条与第 5 条。 ### 2. `capture()` 的两条返回路径必须一起改 工单只覆盖了 `:204` 的主路径。`:180` 的 `root == null` 分支同样调用 `currentActivity()`,若不走同一套按包查询,页面过渡期仍会漏出被污染的 Activity 值。请在方案中显式列出这两处。 ### 3. 根因第 3 条应标注为假设,且修复不应依赖它 「ColorOS 在透明中转页移除后可能不再发送 PDD `TYPE_WINDOW_STATE_CHANGED` 事件」目前没有证据支撑,按 `AGENTS.md` 的事实边界要求应记为待确认假设,不与代码事实混列。 更重要的是:即使该 ROM 行为正常、事件确实补发,仍存在纯代码层面的时序竞争——`ClipboardRelayActivity.readFresh()` 返回后 runner 立即 `capture()`,该事件可能尚未送达。因此「按包隔离」这个修法对该假设不敏感,两种情况都能修好。建议在工单中写明这一点,避免实施时花时间去证伪 ROM 行为。 ### 4. 补充可观测手段 当前 `PDD_DETAIL_ENTRY_FAILED` 把「包名匹配但 Activity 不匹配」和「节点选择器不匹配」归为同一个错误码,这正是本次只能靠 30 秒超时倒推的原因。建议在页面证据等待超时时,复用 #106 的 `AgentDiagnosticStore` 记录一条可区分二者的原因枚举。目标 4 坚持精确 Activity 门禁是对的,但门禁需要配套诊断,否则同类问题复发时仍然缺乏证据。 诊断仍不得记录控件树、截图、链接、goods_id 与剪贴板内容,与本工单安全边界一致。 ### 两点小建议 - 验收标准建议增加一条可量化项:身份识别完成到进入详情采集的间隔小于 3 秒(现有「不再等待约 30 秒」偏定性)。 - 按包缓存会长期保留每个包最后一次已确认 Activity,存在 stale 风险(工单风险段已提及但未给对策)。实际上「当前 root 包名 + 精确 Activity + 节点强证据三项同时匹配」已足以兜住,建议在风险段写明这一结论,不必额外增加机制。 非目标、安全边界、回退方式各段无需调整。
Author
Owner

开始实施。已将全栈评审决策合并进工单正文并删除冲突方案:保留 UiDriver.currentActivity() 无参接口;tracker 使用线程安全的按包证据;capture() 以同一 root 原子生成快照;详情入口复用现有本地诊断表记录安全匹配状态。本次只改 Android,不改服务端、数据库、共享 API、规则和任务时序。

开始实施。已将全栈评审决策合并进工单正文并删除冲突方案:保留 `UiDriver.currentActivity()` 无参接口;tracker 使用线程安全的按包证据;`capture()` 以同一 root 原子生成快照;详情入口复用现有本地诊断表记录安全匹配状态。本次只改 Android,不改服务端、数据库、共享 API、规则和任务时序。
Author
Owner

实施完成,待验收

已按正文中的最终方案完成 Android 修复,提交:e606096e497?(本地提交短哈希 e606096,完整哈希以仓库提交为准)。

实现

  • ActivityEvidenceTracker 改为 ConcurrentHashMap 按包保存已确认 Activity;跨包 Agent 剪贴板中转不再覆盖 PDD 证据,同包新 Activity 仍可更新,PopupWindow 等未声明窗口仍被忽略。
  • 保留 UiDriver.currentActivity() 无参契约;Accessibility Service 根据当前 root 包查询对应 Activity。
  • capture() 只读取一次局部 root,并用其 package/activity/nodes 组成一致快照;root 缺失返回全 null/空节点快照。
  • 当前页面采集的详情入口新增安全的 DETAIL_ENTRY 本地诊断,支持 DETAIL_ENTRY_MATCHED、ROOT_UNAVAILABLE、PACKAGE_MISMATCH、ACTIVITY_MISMATCH、SELECTOR_MISMATCH;仅复用现有字段记录布尔匹配、候选数、次数和耗时。
  • 普通管理端采集、采购路径、服务端、规则、共享 API、SQLite schema/版本均未修改。
  • Agent 版本更新为 0.9.7(versionCode 20)。

自动验证

  • .\android\gradlew.bat -p android :app:testDebugUnitTest :app:assembleDebug:通过。
  • .\scripts\verify.ps1 -Component android:通过(Debug/Release 单元测试和 Debug APK 构建)。
  • python dev_scripts/harness.py check --strict:通过。
  • git diff --check:通过。
  • 新增覆盖:PDD → Agent 透明 Activity 跨包序列、未知包、同包新 Activity、相对类名、PopupWindow、tracker → UiSnapshot → parser 衔接、详情入口成功耗时及 root/package/activity/selector 四类失败诊断。
  • 未发现 Wiki 对诊断阶段/原因的长期枚举;本次为内部实现修复,无长期文档事实变化,因此按正文约定跳过 Wiki 更新与同步。

PKG110

  • 已成功安装 0.9.7(versionCode 20)。
  • 无障碍服务授权仍在。
  • 安装后已启动 Agent 主界面;安装会终止原前台进程,当前连接/采集需在界面重新启动后由人工从已授权 PDD 商品页执行。
  • 尚未代替用户点击真实商品的“当前页面采集”,因此“首次详情证据 < 3 秒”和最终任务状态仍为真机人工验收项;未读取或记录控件树、截图、链接、goods_id、剪贴板正文或商品内容。

工单保持 open / 待验收。

## 实施完成,待验收 已按正文中的最终方案完成 Android 修复,提交:`e606096e497?`(本地提交短哈希 `e606096`,完整哈希以仓库提交为准)。 ### 实现 - `ActivityEvidenceTracker` 改为 `ConcurrentHashMap` 按包保存已确认 Activity;跨包 Agent 剪贴板中转不再覆盖 PDD 证据,同包新 Activity 仍可更新,PopupWindow 等未声明窗口仍被忽略。 - 保留 `UiDriver.currentActivity()` 无参契约;Accessibility Service 根据当前 root 包查询对应 Activity。 - `capture()` 只读取一次局部 root,并用其 package/activity/nodes 组成一致快照;root 缺失返回全 null/空节点快照。 - 当前页面采集的详情入口新增安全的 `DETAIL_ENTRY` 本地诊断,支持 `DETAIL_ENTRY_MATCHED`、`ROOT_UNAVAILABLE`、`PACKAGE_MISMATCH`、`ACTIVITY_MISMATCH`、`SELECTOR_MISMATCH`;仅复用现有字段记录布尔匹配、候选数、次数和耗时。 - 普通管理端采集、采购路径、服务端、规则、共享 API、SQLite schema/版本均未修改。 - Agent 版本更新为 0.9.7(versionCode 20)。 ### 自动验证 - `.\android\gradlew.bat -p android :app:testDebugUnitTest :app:assembleDebug`:通过。 - `.\scripts\verify.ps1 -Component android`:通过(Debug/Release 单元测试和 Debug APK 构建)。 - `python dev_scripts/harness.py check --strict`:通过。 - `git diff --check`:通过。 - 新增覆盖:PDD → Agent 透明 Activity 跨包序列、未知包、同包新 Activity、相对类名、PopupWindow、tracker → UiSnapshot → parser 衔接、详情入口成功耗时及 root/package/activity/selector 四类失败诊断。 - 未发现 Wiki 对诊断阶段/原因的长期枚举;本次为内部实现修复,无长期文档事实变化,因此按正文约定跳过 Wiki 更新与同步。 ### PKG110 - 已成功安装 0.9.7(versionCode 20)。 - 无障碍服务授权仍在。 - 安装后已启动 Agent 主界面;安装会终止原前台进程,当前连接/采集需在界面重新启动后由人工从已授权 PDD 商品页执行。 - 尚未代替用户点击真实商品的“当前页面采集”,因此“首次详情证据 < 3 秒”和最终任务状态仍为真机人工验收项;未读取或记录控件树、截图、链接、goods_id、剪贴板正文或商品内容。 工单保持 open / 待验收。
Author
Owner

更正上一条实施记录中的提交哈希:完整提交为 e606096034dcd9484af213101ea92e24fa48dfdc。上一条中的 e606096e497? 是回写时的占位笔误,不是有效提交哈希;其余实施与验证记录不变。

更正上一条实施记录中的提交哈希:完整提交为 `e606096034dcd9484af213101ea92e24fa48dfdc`。上一条中的 `e606096e497?` 是回写时的占位笔误,不是有效提交哈希;其余实施与验证记录不变。
Author
Owner

用户于 2026-08-28 明确确认本工单验收通过。按项目流程记录验收结论并关闭工单;本次仅更新工单状态,无新增长期文档事实,不重复同步 Wiki。

用户于 2026-08-28 明确确认本工单验收通过。按项目流程记录验收结论并关闭工单;本次仅更新工单状态,无新增长期文档事实,不重复同步 Wiki。
ila closed this issue 2026-08-28 15:07:11 +08:00
Sign in to join this conversation.
No labels
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: OPC/goauto#110