PddProductDetailCollector.moveSpecPanelToTop always swiped DOWN at
least once even when the panel already showed its topmost color
heading, and required two identical viewport signatures to stop. On
a real device that extra swipe could drag the bottom sheet and make
the color/size headings disappear, after which the purchase spec
probe silently reported spec_probe_completed with zero dimensions
and the server reported the generic PURCHASE_SPEC_NOT_MATCHED,
hiding the real cause (goods 8580, tasks 551/552).
- moveSpecPanelToTop now skips the restore swipe when the panel is
already at top (the first parsed dimension is "color" with visible
values), and stops and fails explicitly (SPEC_PANEL_TOP_COLLAPSED)
if a restore swipe makes headings/dimensions vanish, instead of
swiping further or returning an empty success.
- New AgentDiagnosticReason.SPEC_PANEL_TOP_ALREADY /
SPEC_PANEL_TOP_COLLAPSED record swipe count and heading/dimension
counts before/after (booleans/counts only, no page text).
- New PurchaseSpecProbePolicy demotes an Agent spec_probe_completed
outcome with zero collected dimensions into an explicit
PURCHASE_SPEC_PROBE_EMPTY failure ("规格探测未读取到任何颜色或尺码")
before it is persisted/reported, instead of reaching the server as
a normal empty probe.
- Server resolveProbedSpecs uses the same explicit
PURCHASE_SPEC_PROBE_EMPTY code/message when a probe result has zero
colors and zero sizes, as defense in depth for older Agent builds.
Tests: PddProductDetailCollectorTest (already-at-top skips the
restore swipe; not-at-top restores and still collects; vanishing
headings stop swiping and fail), PurchaseSpecProbePolicyTest, and
service_test.go TestLiveProbeWithNoDimensionsFailsWithExplicitEmptyProbeCode.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
Address review findings on 01510a8:
1. BLOCKER: sessionRetryBackoff summed to 30min, shorter than the up-to-
~60min gap between a session dying and the next hourly SYB sync
refreshing it. Changed to 5m/10m/15m/30m/30m (total 90min across
maxSessionRetryAttempts=6), updated the code comment to state the
~90min > one hourly sync period rationale, and added
TestSessionRetryBackoffTotalExceedsHourlySyncWindow to guard it.
2. Test gap: the CheckSession probe added inside
restoreOrderWritebackClient was only exercised through a fake
Factory, never through a real sybclient.Client. Added
httptest-backed tests that run restoreOrderWritebackClient against
an emulated /am/user/get (matching the envelope shape in
sybclient/client.go's `envelope` type): valid session returns a
client, mismatched username maps to ErrSessionInvalid, 5xx/timeout
map to a non-invalid error — each asserting the syb_session row is
left untouched. Added an end-to-end worker test using the real
Factory against the invalid-session server, asserting
failed/SYB_SESSION_UNAVAILABLE with a scheduled backoff and an
intact session row.
3. sessionUnavailableMessage: renamed the default category to
"会话恢复失败(网络/其他)" and wrapped every category in an
actionable template ("SYB会话不可用(<类别>),将自动重试;如持续
失败请恢复登录后重试"), still well under the 300-char column limit
and free of raw error text/credentials.
Tests: go vet ./app/goauto/purchase/... (clean); go test
./app/goauto/purchase/... (ok, 3.4s, includes the new httptest-backed
CheckSession coverage and the backoff-window guard).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
SYB order-number writeback silently gave up on session-class failures
(SYB_SESSION_UNAVAILABLE), requiring manual resubmit even though the
hourly sync job refreshes the session on its own. This adds a bounded,
backoff-scheduled auto-retry for that error code only:
- restoreOrderWritebackClient now actively probes the cached cookie
jar with sybclient.CheckSession after import, so a remotely-expired
session is classified as retryable up front instead of surfacing
later as SYB_READ_FAILED. It never logs in, never triggers OCR and
never deletes the cached session.
- The dropped Factory error is now categorized into a safe message
(no cookies/tokens) and recorded in error_message.
- The worker's claim query additionally picks up failed rows with
error_code=SYB_SESSION_UNAVAILABLE once their backoff
(lease_expires_at) has elapsed and attempt_count is below
maxSessionRetryAttempts=6 (1m/2m/4m/8m/15m growing backoff, chosen
to span the hourly sync window); other failure codes are unchanged.
- CanSubmit no longer hides manual resubmit during that backoff
window; manual resubmit resets attempt_count to 0 and clears the
lease so the worker cannot double-claim the same row.
Diff is limited to the purchase package; sybimport/sybclient/
sybinnercode are untouched.
Tests: go test ./app/goauto/purchase/... (new
order_writeback_session_retry_test.go covers backoff scheduling,
reclaim timing, max-attempt cutoff, CheckSession invalid/network
classification with no session deletion, CanSubmit during backoff,
manual resubmit reset, and non-session codes being excluded).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
Item 5 of #279 required bringing the image-search auto-link annotation
into the purchase-facing views, display-only, without gating anything.
- BatchPreviewItem and AdminTaskItem now carry imageSearchLinked, sourced
read-only from shopee_product.image_search_linked (already loaded in
the batch preview dataset; bulk-loaded for the task list/detail views).
- Batch purchase preview dialog and purchase task list/detail now show a
"图搜未核" tag plus a restrained summary hint when applicable.
- Does not touch Eligible/ReasonCode/Reason/NextAction or the #283
mappingTargetsValid gate; regression test confirms an ineligible row's
outcome is unchanged when imageSearchLinked is true.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F
Server: BatchCreateImageSearch now rejects a batch whose deduplicated
task count (one task per shopee_product, after merging SYB detail
rows) exceeds imageSearchMaxBatchTasks (50), returning
IMAGE_SEARCH_BATCH_TOO_LARGE with a readable Chinese message instead
of silently truncating. The limit is overridable via
GOAUTO_IMAGE_SEARCH_MAX_BATCH_TASKS. #277's scheduling floor (采购 >
采集 > 图搜) isn't implemented yet, so this cap is the only guard
against a large image-search batch starving manually started
collection/purchase tasks.
Web: the batch confirm dialog now shows the deduplicated task count
and an estimated device-occupation time range (20-40s/task), and
disables the submit button with an explanation when the batch would
exceed the same limit, instead of letting the request fail after
submit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NTDbDcwbDw1TSAcE6wfh2F