DM 自动回复 · 两案例根因 + 优化 + 卡点 + 15 秒级路线

2026-07-05 复盘 | 有大有小(浏览器端) + 零星(浏览器端) + PC 端通道 | 施工单 docs/requirements/2026-07-05-dm-autoreply-fix.md
2 PR 已合入 main
一句话:7/3 尚有 5 条成功,7/4–7/5 急转直下 0 成功。浏览器端败于面板 VL 误读花体名(wrong_chat)、主页没落成(wrong_user)、疑限频(rejected),PC 端败于按昵称定位失败(no_row),外加 worker 发完没回调挂死(sending>15min),再被「失败即永久死锁」放大。首触派单同期 213 条成功、不受影响
227/3–7/5 草稿
5成功(全在 7/3)
10failed
6needs_human
~15s优化目标端到端

01两个案例:分通道看

🌐 浏览器端 · Yᗜ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 代码路径 · no_row(例:小文 7/3)

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_webno_row 只出自 PC 代码 reply_in_inbox浏览器号出现 no_row = 该机被误跑成 PC 模式,如雷声/有大有小);卡 sending>15min 是发送后未回调被 sweeper 转人工,两通道通用。

7/3–7/5 逐条失败案例(北京时间,DB 直查)

日期客户账号 / 通道结果具体原因
7/3郭钰饭粒 / 浏览器sent×3 + wrong_user×2sec_uid 直达偶发没落成个人主页(页面没加载好/导航抖动)→ 主页门 ocr_verify 判 wrong_user 不发。同号 3 条又成功 = 间歇性
7/31饭粒 / 浏览器sent×1 + 卡 sending×5客户名「1」是脏 inbound(消息被当昵称);发送后 worker 没回调 complete_dm_reply → 挂 sending,sweeper 15min 后转人工,触发 5 次风暴
7/3外架老鬼有大有小 / 浏览器sent(nurture)—(正常发出,证明浏览器端本身能通)
7/3小文(野荞) / PCno_row按昵称在会话列表精确定位失败
7/4水灵子/易茗/金凌白酒/A.小目标零星 / 浏览器rejected×4发送检测到对方拒收/无法送达,4 条全砸一个号 → 疑该号私信被风控限频(7911),或 _check_rejected 误报(待 P2 拉日志区分)
7/4Seventeen周💍零星 / —needs_human命中「负面/投诉」护栏正确转人工(非 bug
7/5爱笑的人运气不会太差!零星 / 浏览器wrong_chat面板身份门 VL 读长名/emoji 误判≠目标
7/5Yᗜangঞᩚ有大有小 / 浏览器wrong_chat×2VL 把花体名读成 YOang → 假阴性拒发(重试仍败)
7/5雷声有大有小 / 浏览器
误跑 PC 代码
no_row → 已人工回+discarded有大有小是浏览器号,那台被误配成 PC 模式 → 跑了 PC 代码按昵称定位失败。修法=锁 AKKE_DM_SCRIPT 含 web
口径修正:7/3 浏览器端本能发出(外架老鬼/郭钰/「1」共 5 条成功),说明通道没死;是 7/4 零星整天 rejected、7/5 有大有小 wrong_chat 叠加「失败即死锁」,才让 7/4–7/5 呈现 0 成功。

02根因 · 分通道

通道根因本质
浏览器端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 + 回调可靠性

03已做优化:2 个 PR(均已合入 main)

PR层级改动状态
#763
解死锁
P0 · 两通道共用 complete_dm_replyfailed有界重试attempts<3→退回 approved(重试同草稿、无 LLM 重生);≥3needs_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 回滚)。 待激活
已合+同步公共镜像,需云电脑拉新+重启才生效
#763 的价值这是根。没它,任何发送修复都会被历史 failed 堵住(RPC 不重生 + send 只发 approved)。上线后新失败自动补发 3 次,瞬时故障(滚屏没到位/面板没开)大概率自愈。

04还有什么卡点 · 分通道

🌐 浏览器端 剩余卡点

  • #764 需云电脑同步激活:镜像已更新,跑着的旧脚本还会 wrong_chat,直到 cloudpc-dom-dm-restart 拉新+重启。
  • rejected(零星)未解:sec_uid 帮不上,是真送达失败/限频。需拉 worker sent_log/Langfuse 区分账号问题 vs 误报(P2)。
  • 发送慢process_web 用 VL/像素定位,3–4 次 VL 调用 → GUI ~40s(见 §05)。

🖥️ PC 端 剩余卡点

  • 无 sec_uid 天花板:identity 最终仍落在「读昵称」,花体名永远有残余误差,无法像浏览器端那样根治。
  • find_row_rect 加固未做(P1-b):滚顶+core-char 模糊+点开验身份,尚未动手。
  • 误配风险:有大有小(web-only)被跑成 PC 模式产生了 no_row,需锁死 AKKE_DM_SCRIPT 含 web。
存量 failed 未解锁:有界重试只对失败生效。两天里已 failed 的存量(零星 rejected×3 + 「爱笑的人」wrong_chat)不会自愈;Yang/雷声已手动 discard,其余需跑一次性解锁脚本。

05怎么优化到「15 秒级」端到端

现状实测端到端 ≈ 6.5 分钟,其中 C 发送段占 88%(均 340s)。要压到 15 秒级,得同时攻三块延迟——而不是只调一处:

先看 C 发送段为什么慢:自动回复不在私信框就地回,每条走 sec_uid 重开会话

机制:网页版私信箱是全局会话列表,就地回复要先在列表里认出目标会话——认错就把话发给上一个人。所以自动回复每条都从 sec_uid 地址栏直达对方主页 → 点「私信」开一个干净会话 → 身份门校验 → 键入发送process_web)。用「导航到唯一主页」换「绝不发错人」,代价是每条多背 1 次整页重载 + 3~4 次 Qwen VL 调用。DOM 版 send_dom 也走 sec_uid 开会话,区别只在它用 CDP/DOM 定位、零 VL——所以快,见下。)

sec_uid 路径 · 单条最快耗时拆解(GUI 段,固定 sleep 来自 douyin_dm_web_grounded.py

动作机制最快耗时
地址栏直达主页 goto_profileCtrl+L→清空→SendInput 键入 URL→Enter→等整页重载~6.1s(固定 0.8+0.2+0.2+0.4 + 等待 4.5)
主页身份门 ocr_verify1 次 Qwen VL 读主页、确认落到对的人~5–7s(1 VL)
关注 + 点赞最新作品best-effort;无坐标/无作品即跳跳过=0s;真点赞+回主页+重导航 +15~20s
点「私信」开面板 open_dm_panel1 VL 定位灰「私信」按钮 + 点击 + 等 3.5s~8–10s(1 VL + 3.5s)
面板身份门 verify_chat_panel1 VL 读面板顶部对话对象是不是目标~5–7s(1 VL)
输入框键入 + 点发送↑点输入框(1.0)+SendInput 中文键入+点发送(1.5)~4s
送达核对 _check_rejected+_verify_bubbleVL 读拒收提示 + 气泡确认含本文案~5–7s(1 VL)
最快合计 ≈ 35 秒(③关注点赞跳过、每步一次过、零重试)。典型 ≈ 40–60s:命中作品要点赞并重导航(+goto_profile 4.0s)/面板首验假阴性 +2.5s 复核/OCR 判非主页要重导航(wait 逐次 +2.0s)。这 35s 只是 GUI 段——端到端还要叠上下面三块延迟(捕获轮询 15s + 草稿生成 ~48s + 180s 调度节流),才凑成 6.5 分钟。
对照 · 同样走 sec_uid,但换 DOM: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

🌐 浏览器端 → 15s 可达

关键杠杆:发送从 VL 换成 DOM。自动回复现走 process_web(像素/VL,3–4 次 VL ≈ 40s);而 douyin_dm_web_send_dom.pyPlaywright/CDP DOM 定位(连 Edge 9222,确定性、零 VL)。捕获侧早已是 DOM(web_capture_dom)——把发送也统一到 DOM:

  • GUI:40s(4 VL)→ ~10s(DOM 直接定位输入框+发送)
  • 身份:DOM 读 sec_uid/元素,不靠 VL 读花体名 → 顺带根治 wrong_chat

叠加 ②③ 事件驱动:草稿即时 + 独立 send 支线 + DOM ~10s ≈ 15s 端到端可达

🖥️ PC 端 → 十几秒,稳 15s 较难

PC 客户端没有 CDP/DOM,只有 UIA + 像素,天花板比浏览器高。能做的:

  • 就地回复改「未读红点 + 新回复冒泡到顶」确定性定位(去掉按昵称 VL locate + 滚屏空跑)→ GUI 40s → 十几秒(memory「C 方案」)
  • identity 仍靠读昵称,无 sec_uid → 花体名残余误差在
战略建议凡能登网页版的号,一律切浏览器端(DOM + sec_uid 双收益,直达 15s);只有网页版登录被封、被迫留 PC 的号才吃 UIA「红点冒泡」加固,接受十几秒。
目标链路(浏览器端 · 事件驱动 + DOM 发送)
客户回复
↓ ~15s poll / webhook
DOM 捕获 → 即时 chatReply 草稿(~5–8s)
↓ 独立 send 支线,草稿 ready 即领(~5s)
DOM 发送 send_dom:CDP 定位输入框 → 键入 → 发送(~10s,零 VL)
≈ 15 秒级 送达 ✓

06下一步清单

优先级动作通道预期
现在云电脑 cloudpc-dom-dm-restart 拉新+重启,激活 #764浏览器wrong_chat 消除
现在跑一次性存量 failed 解锁脚本两通道解开卡住的会话
P2拉零星 rejected×4 的 worker 日志,定真限频 vs 误报浏览器决定换号 vs 改码
性能自动回复发送从 process_websend_dom(DOM)浏览器GUI 40s→~10s,逼近 15s
性能拆独立 send 支线 + 配 webhook 即时草稿两通道去掉 180s 节流,端到端 6.5min→15s 级
P1-bPC find_row_rect 滚顶+模糊+验身份;能切 web 的号切走PCno_row 收敛到十几秒

07更新部署操作(怎么上线激活)

① #763 解死锁 · 已自动上生产

DB migration,合并 main 即触发 supabase-migrate.ymlsupabase db push。无需人工操作。

② #764 信任 sec_uid · 需在每台云电脑手动激活

改的是云电脑脚本 douyin_dm_web_grounded.py不经 Fly 部署。合并已把新脚本同步到公共镜像(sync-wuying-scripts = success),但跑着的旧进程需拉新+重启才生效。在每台跑浏览器端 DOM DM 的云电脑(有大有小=野荞机、零星=饭粒机)上:

激活步骤(照 skill cloudpc-dom-dm-restart 的更新流程)
1. 停掉当前 DOM 自动回复 poll agent / autoreply 进程
2. 从镜像重新拉脚本覆盖本地
raw.githubusercontent.com/upioai/wiki/main/public/akke/wuying-dm/douyin_dm_web_grounded.py
3. 确认 env(默认已开,无需改):AKKE_WEB_TRUST_SECUID_ON_PANEL=1(=0 可一键回滚)
4. 重启 DOM 自动回复 + 验活
验证:拿一个花体/长名客户跑一轮自动回复,日志应从 wrong_chat 不发 变成 [软告警]…sec_uid 直达已锁人 → 照发,且气泡验证通过。
顺带治误配:有大有小是 web-only 号,确保其 AKKE_DM_SCRIPT 恒含 web(别被跑成 PC 模式,否则复现雷声 no_row)。

③ 存量 failed 解锁 · 跑一次性脚本

有界重试只对失败生效;7/3–7/5 已 failed 的存量不会自愈。Yang/雷声已手动 discard,其余(零星 rejected×4、爱笑的人 wrong_chat、郭钰 wrong_user、小文 no_row)需按情况处理:

_ 前缀诊断脚本 dry-run 先看清单,确认后 --apply

④ 浏览器号发送通道:私信框 → sec_uid 直达 · 每台云电脑手动切

两条发送路,代码里靠一个 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_webURL 唯一锁人(sec_uid 由 claim_dm_replies 返回)wrong_chat(面板 VL 误读花体名,#764 已修
为什么切 sec_uid:URL 唯一锁人根治「私信框认不出会话行 no_row / 认错人」——这正是小文 no_row、有大有小误配跑 PC 的病根。代价是每条要整页重导航(慢,见 §05),但「绝不发错人」优先;提速另由 #764 + VL→DOM 解决,不是靠回退私信框。

切换只改一个 envAKKE_DM_SCRIPT 的值web → auto-reply 自动走 sec_uid(reply_in_web);值是 douyin_dm_grounded.py(不含 web)= 走私信框(reply_in_inbox)。下面在每台跑该浏览器号自动回复的云电脑 PowerShell 里一步步执行(云电脑路径 C:\akke-wuying\wuying-dm)。

前提该号网页版 Edge 在线(隐形 Edge :9222 已起、能读到私信列表)。登录过期先按 skill cloudpc-dom-dm-restart 的【登录过期兜底】可见窗口重登,否则 sec_uid 直达也落不到主页 → wrong_user。
步骤 1查现状:现在走的是哪条路
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 那行在。

步骤 2 · 修改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 }
步骤 3确认改对了
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(有重复行删掉旧的)。

步骤 4 · 部署重启 poll agent(python-dotenv 只在启动读 .env,不重启不生效)
# 杀掉旧 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
步骤 5 · 验活念黑窗日志对照
发送段日志从 === 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)。
回退想退回私信框:.envAKKE_DM_SCRIPT 改回 douyin_dm_grounded.py(不含 web)→ 重启 poll agent(步骤 4)。
一句话别忘:sec_uid 只解决「发对人」(根治 no_row/wrong_user),不提速——每条仍背整页重导航(~35s GUI,见 §05 拆解);真正快靠把发送从 process_websend_dom(DOM,~10s)。