fix(desktop-client): stop false manual-verification states and auto-revive probes after sleep/offline

Challenge detection previously regex-matched the entire page innerText, so
chat transcripts or console copy containing words like 验证码/安全验证/verify
flagged healthy accounts as challenge_required. Detection is now scoped to
real evidence only (visible captcha vendor widgets, short modal copy, sparse
interstitial pages) via a shared challenge-signals module used by the generic
AI probe, doubao/kimi/qwen auth rules, and the deepseek runtime snapshot.
Platform API error codes (e.g. baijiahao_challenge_required) keep working.

Account health also could not recover after lid-close sleep or a network
drop: offline probes misread cached console pages as expired, and expired
records null out nextProbeAt so they were never re-probed. Probing is now
skipped while offline (network_error backoff instead), and powerMonitor
resume/unlock plus an offline-to-online transition watch schedule jittered
recovery probes for all tracked accounts, including expired ones. Challenge
rechecks wait 8s so transient widgets during page load no longer stick.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 11:06:51 +08:00
parent c7bad83496
commit abacc1f421
7 changed files with 287 additions and 77 deletions
@@ -2379,20 +2379,72 @@ async function readDeepSeekPageSnapshot(
}
const bodyText = normalize(document.body?.innerText || '')
// 登录/验证判定不能直接对整页 innerText 匹配:聊天问题与回答里出现
// "登录/验证码/安全验证" 这类词会把正常会话误判成需要人工处理。
// 只认三类证据:sign_in 路由、可见弹层内的短文案、稀疏拦截页。
const loginPhrasePattern =
/(log in|send code|scan with wechat to login|phone number|continue with deepseek|和 deepseek 继续聊|登录|发送验证码|手机号|验证码)/i
const challengePhrasePattern =
/(人机验证|安全验证|请完成验证|完成下方验证|拖动滑块|滑动验证|向右滑动|安全检测|访问异常|环境异常|verification required|security check|unusual traffic|not a robot|请输入(?:图形)?验证码)/i
const findVisibleSurfaceText = (
selectors: string,
pattern: RegExp,
maxTextLength: number,
): boolean => {
for (const node of Array.from(document.querySelectorAll(selectors)).slice(0, 50)) {
if (!isVisible(node)) {
continue
}
const text = normalize(node.innerText || node.textContent)
if (text && text.length <= maxTextLength && pattern.test(text)) {
return true
}
}
return false
}
const hasChallengeVendorWidget = (() => {
const vendorSelectors = [
'[class*="geetest_"]',
'[id^="nc_"][id$="_wrapper"]',
'.nc-container',
'[class*="nocaptcha"]',
'[class*="yidun_modal"]',
'[class*="yidun_popup"]',
'[class*="vaptcha"]',
'[class*="tcaptcha"]',
'#tcaptcha_iframe',
'[class*="captcha_verify"]',
'[class*="secsdk-captcha"]',
'iframe[src*="captcha"]',
'iframe[src*="geetest"]',
'iframe[src*="turnstile"]',
'iframe[src*="hcaptcha"]',
'iframe[src*="recaptcha"]',
'iframe[title*="验证"]',
].join(',')
for (const node of Array.from(document.querySelectorAll(vendorSelectors)).slice(0, 50)) {
if (isVisible(node)) {
return true
}
}
return false
})()
const loginRequired =
window.location.pathname.includes('/sign_in') ||
Boolean(
bodyText &&
/(log in|send code|scan with wechat to login|phone number|continue with deepseek|和 deepseek 继续聊|登录|发送验证码|手机号|验证码)/i.test(
bodyText,
),
)
const challengeRequired = Boolean(
bodyText &&
/(verification required|captcha|security check|verify|challenge|人机验证|安全验证|请完成验证|验证码|风控|环境异常)/i.test(
bodyText,
),
)
findVisibleSurfaceText(
'[role="dialog"], [aria-modal="true"], [class*="modal"], [class*="login"], [class*="sign"], [class*="auth"]',
loginPhrasePattern,
300,
) ||
Boolean(bodyText && bodyText.length <= 600 && loginPhrasePattern.test(bodyText))
const challengeRequired =
hasChallengeVendorWidget ||
findVisibleSurfaceText(
'[role="dialog"], [aria-modal="true"], [class*="captcha"], [id*="captcha"], [class*="verify"], [class*="Verify"]',
challengePhrasePattern,
240,
) ||
Boolean(bodyText && bodyText.length <= 400 && challengePhrasePattern.test(bodyText))
const busyDescriptors: string[] = []
for (const node of Array.from(