Review follow-up on 2bc624f:
1. finalSubmitTargets' payment-word block only covered PAYMENT_MARKERS
(立即支付/确认支付/输入支付密码). The ticket also requires blocking 去支付
and 付款. These cannot join the shared PAYMENT_MARKERS list: it also
gates the global pageProblem() payment guard, and UNPAID_MARKERS' "待付款"
contains "付款", so adding it there would misclassify every ordinary
unpaid-order page as a forbidden payment page. Added a separate
SUBMIT_TARGET_BLOCKED_MARKERS list (立即支付/确认支付/去支付/付款/输入支付密码)
used only by finalSubmitTargets' bottom-node subtree check.
2. Candidates were previously scanned across the whole snapshot, so an
unrelated clickable node outside the recognized panel (e.g. an
underlying goods-detail-page bottom bar still in the accessibility tree
behind the sheet) could out-rank the real button by sitting lower on
screen. Added panelContainerBounds(): climbs from the panel's unique
quantity input to the largest ancestor that still does not cover the
whole screen (the same "does not cover the whole screen" bounded notion
#331 already uses for sharesBoundedPanelContainer/boundedScrollables),
giving the whole bottom-sheet container. ParsedPddScreen.specPanelContainer
(PddScreenParser's `panelScrollable`) was considered but is the wrong
notion here: it is only the inner *scrollable dimension list* used for
heading/option parsing, and in a real PDD sheet the address/payment/
submit rows sit outside it as structural siblings, not descendants -
using it would incorrectly exclude the real submit row in most panels.
A candidate belongs to the panel when its center point falls inside the
container's bounds (geometric containment, matching the existing
`inside()` convention in this file, not path prefix, since PDD's own
tree can place the submit bar as a structural sibling that is still
visually part of the sheet). When the quantity input is missing or not
unique, the container cannot be determined and finalSubmitTargets fails
explicitly (no target) instead of guessing.
Tests added to SpecPanelRecognitionTest: bottom node labeled "去支付" and
"付款" are blocked (distinct from the existing PAYMENT_MARKERS-triggered
PURCHASE_PAYMENT_FORBIDDEN cases, since neither word reaches the global
guard); a clickable node outside the recognized panel container is never
chosen even though it is bottom-most on screen; a recognized panel with no
determinable quantity input/container fails explicitly. All existing
readOrderResult tests (which legitimately show "待付款"/"去支付" on the
order-result page, unrelated to finalSubmitTargets) stay green unchanged,
confirming SUBMIT_TARGET_BLOCKED_MARKERS did not leak into that path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
Android Agent
一期 Android 执行端,包名为 cn.ilapage.goauto.agent。
当前已经实现:
- 首次安装生成并持久化随机 UUID
installId,不读取硬件唯一标识。 - 自动注册和已认证的版本信息更新,无注册码输入页。
- Android Keystore AES-GCM 加密保存 Device Token,界面和日志不显示明文。
- 前台服务、15 秒心跳、网络恢复触发重连。
- Agent 0.3.1 的状态、采集、采购、设置四 Tab 外壳;默认进入状态页。
- 状态页区分无障碍未开启、系统已开启但服务未绑定、已绑定就绪三态。
- 设置页可测试服务器连接、保存服务器地址和设备名称并重新连接;任务执行中禁止修改。
- 设置页只显示 Device Token 是否配置,不显示 Token 明文;采集和采购 Tab 在 #90 接入当前设备任务历史。
- 清除 App 数据后按新的安装实例注册;Token 无效时明确报错,不降级为无认证注册。
服务端地址可在设置页保存,也可在构建时通过 GOAUTO_SERVER_URL 环境变量或同名 Gradle 属性写入默认值。Debug 和 Release 均接受 HTTP 或 HTTPS Origin;HTTP 会明文传输 Device Token、任务内容和执行结果,仅应连接到管理员明确启用明文 Agent 流量的受控服务。设置页的“测试连接”只访问 /api/v1/health,不会注册设备或创建任务;“保存并重新连接”沿用当前 installId 和 Device Token。
设备执行任务时,状态页显示当前任务,设置页会禁用服务器地址、设备名称、测试和保存操作。返回系统无障碍设置后,状态页和设置页会自动刷新真实就绪状态。
任务结果成功提交(采购结果也可以先安全写入本地 Outbox)后,Agent 进入 15 秒自动返回窗口。它沿用现有任务轮询依次确认采购、采集队列都为空,并确认用户没有离开本次自动化打开的 PDD/浏览器页面,才返回 Agent 状态页。新任务、网络异常、待恢复的不可逆采购边界或前台应用变化都会取消自动返回。
采集结果被服务端安全接收后还会按设置页的“采集任务执行间隔”等待下一次采集;起止值均为 0~600 秒整数,默认 15~15,每次在闭区间内随机一次。采购仍可优先执行,手动检查和重新采集不能绕过;本次抽中的秒数、开始时间和截止时间会一起持久化,重启或修改设置不会重新随机。该间隔与自动返回窗口相互独立。任务执行、自动返回窗口和采集间隔期间有界保持亮屏,结束后释放;不会自动解锁 PIN、图案或密码。
T05 真机验证记录
- 一加 PKG110 / Android 16:首次注册、15 秒心跳、前台通知和在线空闲状态已验证。
- 杀死进程后重新打开:installId 和 deviceId 保持不变,设备表仍只有一条记录,心跳恢复。
- PDD 8.17.0 版本信息已随注册提交。
- ColorOS 首次 ADB 安装需要在“安装增强防护”页执行“更多 → 开始深度扫描 → 继续安装”。
- 当前 MVP 只完成并维护一加/ColorOS 真机兼容,不包含华为 ROM。
T23~T24 v2 真机验证
- 一加 PKG110 / Android 16 已验证 v2 能力上报、正式任务领取、浏览器到 PDD
NewPageActivity、规格面板打开、颜色文字点击和逐颜色稳定价格。 - 图片型颜色卡片只点击文字节点,不点击整卡或“打开大图/查看大图”。
- 规格值会排除“#一次选多款#”等面板功能入口,避免把操作按钮伪装成尺码和 SKU。
- 商品
719834019024已采到 4 个颜色、4 个 2690 分颜色价格和 4 个结构化 SKU,缺少尺码、店铺和评价时正确提交completed_partial。 - 商品
236231603269已验证规格面板归顶、锚定横纵容器、逐行蛇形颜色遍历和尺码续页:14 个颜色、14 个颜色价格、8 个尺码和 112 个完整可用 SKU,任务最终为completed。 - 滚动优先使用目标容器的无障碍
ACTION_SCROLL_FORWARD/BACKWARD;回退坐标手势时等待系统完成回调后才读取新树,避免把未结束手势误判成列表边缘。 - 商品标题回退只接受与销量节点空间关联的文本,不再把页面中任意最长文本当成标题。
- 规格入口只接受带明确选择语义的规格摘要,或屏幕底部真实“购买/拼单”文字按钮;评价、评论、晒单和问答区域及其父容器不能成为入口。
- 商品评价页与商品详情页共用
NewPageActivity时,Agent 通过顶部评价标题和评价内容识别误触,安全返回后最多重新定位一次。点击无页面变化、重复误入评价页和面板结构无法确认使用不同错误码;日志只记录入口语义、控件类型、坐标和动作结果,不保存控件树或截图。 - 逐颜色价格必须先取得目标选中、已选摘要或规格面板变化证据,再等待价格刷新并连续稳定读取;其他颜色残留的选中标记不会阻塞新价格,相同价格仍允许保存。动作无效果时不会沿用旧价格,结构化日志区分未确认选择、未找到价格和价格不稳定。
.\gradlew.bat test
.\gradlew.bat assembleDebug
APK 输出:app/build/outputs/apk/debug/app-debug.apk。AccessibilityService、任务领取和 PDD 页面执行由后续工单实现。