docs/requirements/2026-07-05-dm-autoreply-fix.mdYᗜangঞᩚ → wrong_chat发送走 sec_uid 地址栏直达主页(URL 唯一锁人),但发送前 verify_chat_panel 又用 VL 读面板顶部昵称二次校验。花体名 Yᗜangঞᩚ 被 VL 读成 YOang → 判「对话对象≠目标」→ 假阴性拒发。
来源:douyin_dm_web_grounded.py:318。重试 2 次都 wrong_chat,客户 4 小时没被回。
PC 客户端就地回复不搜号,find_row_rect 用 _norm(name)==target 在会话列表精确匹配 + 不滚动找那一行,miss → no_row 不发(douyin_dm_reply_inbox.py:65;DOM 发送返回的是 error:no_target,不是 no_row)。
⚠️ 雷声(有大有小)也报 no_row,但有大有小是浏览器号——是它被误配成 PC 模式才跑了这条 PC 代码,不是真 PC 通道案例。
wrong_chat/rejected/wrong_user 只出自浏览器端 process_web;no_row 只出自 PC 代码 reply_in_inbox(浏览器号出现 no_row = 该机被误跑成 PC 模式,如雷声/有大有小);卡 sending>15min 是发送后未回调被 sweeper 转人工,两通道通用。| 日期 | 客户 | 账号 / 通道 | 结果 | 具体原因 |
|---|---|---|---|---|
| 7/3 | 郭钰 | 饭粒 / 浏览器 | sent×3 + wrong_user×2 | sec_uid 直达偶发没落成个人主页(页面没加载好/导航抖动)→ 主页门 ocr_verify 判 wrong_user 不发。同号 3 条又成功 = 间歇性 |
| 7/3 | 1 | 饭粒 / 浏览器 | sent×1 + 卡 sending×5 | 客户名「1」是脏 inbound(消息被当昵称);发送后 worker 没回调 complete_dm_reply → 挂 sending,sweeper 15min 后转人工,触发 5 次风暴 |
| 7/3 | 外架老鬼 | 有大有小 / 浏览器 | sent(nurture) | —(正常发出,证明浏览器端本身能通) |
| 7/3 | — | 小文(野荞) / PC | no_row | 按昵称在会话列表精确定位失败 |
| 7/4 | 水灵子/易茗/金凌白酒/A.小目标 | 零星 / 浏览器 | rejected×4 | 发送检测到对方拒收/无法送达,4 条全砸一个号 → 疑该号私信被风控限频(7911),或 _check_rejected 误报(待 P2 拉日志区分) |
| 7/4 | Seventeen周💍 | 零星 / — | needs_human | 命中「负面/投诉」护栏正确转人工(非 bug) |
| 7/5 | 爱笑的人运气不会太差! | 零星 / 浏览器 | wrong_chat | 面板身份门 VL 读长名/emoji 误判≠目标 |
| 7/5 | Yᗜangঞᩚ | 有大有小 / 浏览器 | wrong_chat×2 | VL 把花体名读成 YOang → 假阴性拒发(重试仍败) |
| 7/5 | 雷声 | 有大有小 / 浏览器 误跑 PC 代码 | no_row → 已人工回+discarded | 有大有小是浏览器号,那台被误配成 PC 模式 → 跑了 PC 代码按昵称定位失败。修法=锁 AKKE_DM_SCRIPT 含 web |
| 通道 | 根因 | 本质 |
|---|---|---|
| 浏览器端 | sec_uid 已唯一锁人,面板门却再用 VL 读昵称二次校验,遇花体/emoji/长名误读 → 假阴性拒发 | 多余的假阳性拦截(主页门早已信任 sec_uid,面板门没跟上→不一致) |
| PC 端 | find_row_rect 精确等值匹配 + 不滚动;无 sec_uid,identity 只能靠读昵称 | 定位机制脆 + 无唯一锁人手段的天花板 |
| 两通道共用 | 失败即永久死锁:dm_reply_drafts 双层(唯一索引 + awaiting RPC NOT EXISTS)都以 status<>'discarded' 判占位,而失败写死 failed | 放大器:单次发送失败=该客户永久沉默,把偶发变成两天全灭 |
| 浏览器端(零星) | rejected×4 集中一个号:真限频(7911) 还是 _check_rejected 误报待查 | 疑账号被私信风控(待 P2 调查,不是这轮能定) |
| 浏览器端(郭钰) | wrong_user:sec_uid 直达偶发没落成个人主页 → 主页门 ocr_verify 拒发 | 页面加载/导航抖动的瞬时故障(同号也有成功)→ #763 有界重试正好覆盖 |
| 两通道(饭粒「1」) | 卡 sending>15min:发送后 worker 未回调 complete_dm_reply → 挂 sending,sweeper 转人工 | 脏 inbound(「1」当昵称)+ 回调缺失;#763 不覆盖此路径(它没走 failed),需单独治脏 inbound + 回调可靠性 |
| PR | 层级 | 改动 | 状态 |
|---|---|---|---|
| #763 解死锁 | P0 · 两通道共用 | complete_dm_reply 收 failed 改有界重试:attempts<3→退回 approved(重试同草稿、无 LLM 重生);≥3→needs_human(人工接管、可见、不 rot)。重试纯确定性 SQL,worker 零改动。 |
已上生产 migrate success · 生产库 attempts 列已验 |
| #764 信任 sec_uid | P1-a · 浏览器端 | 面板身份门在 sec_uid 直达路径降级为软告警(与主页门 TRUST_SECUID 对称),照发;抖音号搜索通道仍严格拦。新增 env AKKE_WEB_TRUST_SECUID_ON_PANEL(默认 1,=0 回滚)。 |
待激活 已合+同步公共镜像,需云电脑拉新+重启才生效 |
cloudpc-dom-dm-restart 拉新+重启。process_web 用 VL/像素定位,3–4 次 VL 调用 → GUI ~40s(见 §05)。find_row_rect 加固未做(P1-b):滚顶+core-char 模糊+点开验身份,尚未动手。AKKE_DM_SCRIPT 含 web。failed 的存量(零星 rejected×3 + 「爱笑的人」wrong_chat)不会自愈;Yang/雷声已手动 discard,其余需跑一次性解锁脚本。现状实测端到端 ≈ 6.5 分钟,其中 C 发送段占 88%(均 340s)。要压到 15 秒级,得同时攻三块延迟——而不是只调一处:
sec_uid 地址栏直达对方主页 → 点「私信」开一个干净会话 → 身份门校验 → 键入发送(process_web)。用「导航到唯一主页」换「绝不发错人」,代价是每条多背 1 次整页重载 + 3~4 次 Qwen VL 调用。(DOM 版 send_dom 也走 sec_uid 开会话,区别只在它用 CDP/DOM 定位、零 VL——所以快,见下。)douyin_dm_web_grounded.py)| 步 | 动作 | 机制 | 最快耗时 |
|---|---|---|---|
| ① | 地址栏直达主页 goto_profile | Ctrl+L→清空→SendInput 键入 URL→Enter→等整页重载 | ~6.1s(固定 0.8+0.2+0.2+0.4 + 等待 4.5) |
| ② | 主页身份门 ocr_verify | 1 次 Qwen VL 读主页、确认落到对的人 | ~5–7s(1 VL) |
| ③ | 关注 + 点赞最新作品 | best-effort;无坐标/无作品即跳 | 跳过=0s;真点赞+回主页+重导航 +15~20s |
| ④ | 点「私信」开面板 open_dm_panel | 1 VL 定位灰「私信」按钮 + 点击 + 等 3.5s | ~8–10s(1 VL + 3.5s) |
| ⑤ | 面板身份门 verify_chat_panel | 1 VL 读面板顶部对话对象是不是目标 | ~5–7s(1 VL) |
| ⑥ | 输入框键入 + 点发送↑ | 点输入框(1.0)+SendInput 中文键入+点发送(1.5) | ~4s |
| ⑦ | 送达核对 _check_rejected+_verify_bubble | VL 读拒收提示 + 气泡确认含本文案 | ~5–7s(1 VL) |
goto_profile 4.0s)/面板首验假阴性 +2.5s 复核/OCR 判非主页要重导航(wait 逐次 +2.0s)。这 35s 只是 GUI 段——端到端还要叠上下面三块延迟(捕获轮询 15s + 草稿生成 ~48s + 180s 调度节流),才凑成 6.5 分钟。douyin_dm_web_send_dom.py 连隐形 Edge :9222,用 CDP DOM 直接定位输入框([data-e2e="msg-input"]),零 VL、零像素定位→ 同一条 sec_uid 开会话+键入+发送压到 ~10s。身份靠 DOM 读元素、不靠 VL 读花体名,顺带根治 wrong_chat。把 35s 砍到 10s 的杠杆是「VL→DOM」,不是「换掉 sec_uid」(sec_uid 是身份保险,两条路都留)。| 延迟段 | 现状 | 15s 级手段 | 目标 |
|---|---|---|---|
| ① 捕获轮询 | poll agent 15s 一轮(已优化) | 保持;或事件驱动 | ~15s |
| ② 草稿生成(A) | ~48s(webhook 未配 → */5 cron 兜底节流) | 配 webhook:客户回复即触发 chatReply,不等 cron | ~5–8s(LLM) |
| ③ 调度到发送(C-排队) | send 与 capture 同子进程,被 AKKE_DM_AUTOREPLY_INTERVAL(默认180s) 节流 | 拆独立 send 支线挂主循环短轮询 / 草稿 ready 即发 | ~5s |
| ④ GUI 发出 | ~30–40s(见下方分通道) | 见下方分通道 | 浏览器 ~10s |
关键杠杆:发送从 VL 换成 DOM。自动回复现走 process_web(像素/VL,3–4 次 VL ≈ 40s);而 douyin_dm_web_send_dom.py 是 Playwright/CDP DOM 定位(连 Edge 9222,确定性、零 VL)。捕获侧早已是 DOM(web_capture_dom)——把发送也统一到 DOM:
叠加 ②③ 事件驱动:草稿即时 + 独立 send 支线 + DOM ~10s ≈ 15s 端到端可达。
PC 客户端没有 CDP/DOM,只有 UIA + 像素,天花板比浏览器高。能做的:
| 优先级 | 动作 | 通道 | 预期 |
|---|---|---|---|
| 现在 | 云电脑 cloudpc-dom-dm-restart 拉新+重启,激活 #764 | 浏览器 | wrong_chat 消除 |
| 现在 | 跑一次性存量 failed 解锁脚本 | 两通道 | 解开卡住的会话 |
| P2 | 拉零星 rejected×4 的 worker 日志,定真限频 vs 误报 | 浏览器 | 决定换号 vs 改码 |
| 性能 | 自动回复发送从 process_web 切 send_dom(DOM) | 浏览器 | GUI 40s→~10s,逼近 15s |
| 性能 | 拆独立 send 支线 + 配 webhook 即时草稿 | 两通道 | 去掉 180s 节流,端到端 6.5min→15s 级 |
| P1-b | PC find_row_rect 滚顶+模糊+验身份;能切 web 的号切走 | PC | no_row 收敛到十几秒 |
DB migration,合并 main 即触发 supabase-migrate.yml 跑 supabase db push。无需人工操作。
Push Supabase Migrations = successdm_reply_drafts.attempts 列supabase/migrations-rollback/20260705225827_dm_reply_bounded_retry_rollback.sql改的是云电脑脚本 douyin_dm_web_grounded.py,不经 Fly 部署。合并已把新脚本同步到公共镜像(sync-wuying-scripts = success),但跑着的旧进程需拉新+重启才生效。在每台跑浏览器端 DOM DM 的云电脑(有大有小=野荞机、零星=饭粒机)上:
cloudpc-dom-dm-restart 的更新流程)raw.githubusercontent.com/upioai/wiki/main/public/akke/wuying-dm/douyin_dm_web_grounded.pywrong_chat 不发 变成 [软告警]…sec_uid 直达已锁人 → 照发,且气泡验证通过。AKKE_DM_SCRIPT 恒含 web(别被跑成 PC 模式,否则复现雷声 no_row)。有界重试只对新失败生效;7/3–7/5 已 failed 的存量不会自愈。Yang/雷声已手动 discard,其余(零星 rejected×4、爱笑的人 wrong_chat、郭钰 wrong_user、小文 no_row)需按情况处理:
discarded(解占位)approved + 重置 attempts=0(激活 #764 后再放,避免又撞 wrong_chat)用 _ 前缀诊断脚本 dry-run 先看清单,确认后 --apply。
两条发送路,代码里靠一个 env 分流(douyin_dm_autoreply.py:202 is_web = "web" in AKKE_DM_SCRIPT):
| 路径 | 怎么发 | 定位方式 | 失败模式 |
|---|---|---|---|
私信框reply_in_inbox | 在网页私信会话列表就地认出目标会话点开回(不导航) | 按昵称精确匹配会话行、不滚动 | no_row(认不出行 / 花体名不匹配)· 认错行=发给上一个人 |
sec_uid 直达reply_in_web | 每条从 sec_uid 地址栏直达对方主页 → 点「私信」开干净会话 → 发(process_web) | URL 唯一锁人(sec_uid 由 claim_dm_replies 返回) | wrong_chat(面板 VL 误读花体名,#764 已修) |
切换只改一个 env:AKKE_DM_SCRIPT 的值含 web → auto-reply 自动走 sec_uid(reply_in_web);值是 douyin_dm_grounded.py(不含 web)= 走私信框(reply_in_inbox)。下面在每台跑该浏览器号自动回复的云电脑 PowerShell 里一步步执行(云电脑路径 C:\akke-wuying\wuying-dm)。
cloudpc-dom-dm-restart 的【登录过期兜底】可见窗口重登,否则 sec_uid 直达也落不到主页 → wrong_user。cd C:\akke-wuying\wuying-dm Select-String -Path .env -Pattern "AKKE_DM_SCRIPT|AKKE_WEB_TRUST_SECUID_ON_PANEL"
看到 AKKE_DM_SCRIPT=douyin_dm_grounded.py(或没这行)= 现在走私信框,需要改;已经是 ...web_send_dom.py / ...web_grounded.py(含 web)= 早已走 sec_uid,只需确认 #764 那行在。
AKKE_DM_SCRIPT 改成含 web 的值 + 补 #764 开关# 先备份 .env Copy-Item .env .env.bak -Force # 把 AKKE_DM_SCRIPT 这行替换成含 web 的值(DOM 机标准值;纯 VL 机用 douyin_dm_web_grounded.py) (Get-Content .env) -replace '^AKKE_DM_SCRIPT=.*','AKKE_DM_SCRIPT=douyin_dm_web_send_dom.py' | Set-Content .env -Encoding ascii # 若原本没有这行,补一行 if(-not (Select-String -Path .env -Pattern '^AKKE_DM_SCRIPT=' -Quiet)){ Add-Content .env "AKKE_DM_SCRIPT=douyin_dm_web_send_dom.py" -Encoding ascii } # 确认 #764 面板信任 sec_uid(缺则补,避免 sec_uid 路径栽面板 wrong_chat) if(-not (Select-String -Path .env -Pattern '^AKKE_WEB_TRUST_SECUID_ON_PANEL=' -Quiet)){ Add-Content .env "AKKE_WEB_TRUST_SECUID_ON_PANEL=1" -Encoding ascii }
Select-String -Path .env -Pattern "AKKE_DM_SCRIPT|AKKE_WEB_TRUST_SECUID_ON_PANEL"
应看到 AKKE_DM_SCRIPT=douyin_dm_web_send_dom.py(含 web)+ AKKE_WEB_TRUST_SECUID_ON_PANEL=1,且只有一行 AKKE_DM_SCRIPT(有重复行删掉旧的)。
# 杀掉旧 poll agent Get-CimInstance Win32_Process | Where-Object {$_.CommandLine -like '*wuying_poll_agent*'} | ForEach-Object { Stop-Process -Id $_.ProcessId -Force } # 重新起(两条腿:捕获回复 + 发送 都在里面) cd C:\akke-wuying\wuying-dm py wuying_poll_agent.py
=== send: … (就地回复·不搜号)=== 变成 === send: … (web·sec_uid 直达主页发)===,每条发出是 [reply] xxx: 已发 ✓;no_row / error:no_target 不再出现。捕获侧同时出 web capture[DOM](AKKE_DM_SCRIPT 含 web 时整通道自动切 web:捕获走 douyin_dm_web_capture、poll agent 自动 HANDLE_RESOLVE=0/ENRICH_REQUIRE_NUMBER=0)。.env 把 AKKE_DM_SCRIPT 改回 douyin_dm_grounded.py(不含 web)→ 重启 poll agent(步骤 4)。process_web 切 send_dom(DOM,~10s)。