WALL 01 · 备案回调域名要 ICP 主体校验
微信客服 kf API 是官方唯一明确支持「AI 代员工回复外部客户」的通道。全链路代码 6 月 10 日就合并部署了,配置卡在第一步:企微保存回调 URL 时先校验域名 ICP 备案主体必须与企业主体一致。vercel.app / fly.dev / upio.ai 全被拒,裸 IP 也拒,报 Domain entity verification failed。
两台在成都的 Windows 云电脑,登录着真实企业微信账号,自己读屏、自己生成话术、自己打字发送。没有人在它们面前,运维它们的是杭州一台 Mac 终端里的 Claude Code。这篇讲两件事:这条遥控链是怎么搭的,以及这套企微代回七个月里迭代成了今天这样。
// 面向工程、运维与产品 · 关键数字标注口径与日期 · 数据截至 2026-07-26
正常做法当然是调接口。2026 年 6 月我们真的把官方接口那条路走完了——代码全部写完上线,然后卡死在一个和技术无关的地方。下面三堵墙,每一堵都实测撞过,不是推断。
微信客服 kf API 是官方唯一明确支持「AI 代员工回复外部客户」的通道。全链路代码 6 月 10 日就合并部署了,配置卡在第一步:企微保存回调 URL 时先校验域名 ICP 备案主体必须与企业主体一致。vercel.app / fly.dev / upio.ai 全被拒,裸 IP 也拒,报 Domain entity verification failed。
业务动线是加好友后 1:1 单聊,不是客服会话。企微在这条动线上没有任何已验证的合规 AI 代答 API——官方能给的只有会话存档(读,约 ¥1800/号/年)和侧边栏 JS-SDK(要人点一下才发)。全自动 + 1:1 单聊这个组合,官方接口给不了。
那就退到桌面自动化——也不顺。企微 4.0+ 是 DirectUI 自绘,UIAutomation 深扫 12 层,会话列表 / 消息文字 / 输入框 全部为 0;CDP 只能挂到「文档」那个 webview,聊天窗是原生绘制。四条结构化读法全死,读消息只剩「截图给视觉模型看」。
三堵墙叠起来只剩一条路:在一台登录着真账号的 Windows 上,像人一样点鼠标敲键盘。而一台要 7×24 常开的 Windows,不可能靠人天天远程桌面连上去伺候——于是就有了下面这条遥控链。
先说清一件容易误会的事:Claude 不在云电脑里面跑。它跑在杭州一台 Mac 的终端里,手上只有一把阿里云 RAM 凭据和一条异步的远程命令 API。所有能力都是从这条窄管子里长出来的。
aliyun ecd run-command。命令体上限 16 KB,默认超时 300 秒
InvokeId,不等结果。区域必须显式写两处(--region + --biz-region-id),否则查不到任何机器
nt authority\system 在 Session 0 执行。它看不见任何 GUI——装依赖、写文件、改 .env、起服务都行,弹窗和点击一律无效
InteractiveToken 计划任务,它会在已登录的 Session 1 交互桌面里以用户令牌运行,pyautogui 截屏/点击/打字全部可用
describe-invocations 取 stdout —— 必须带 --include-output true,否则 Output 字段恒空。输出 >24 KB 会截断,丢弃字符数记在 Dropped
openrouter.ai / ntfy.sh / GitHub raw 全被 按 SNI 阻断。视觉模型和大脑端点都改走东京 Fly worker 透传
这一层的完整版(官方接口口径全表、读回通道可信度实测、把 88 KB 脚本塞进 16 KB 管子的分块校验法、十四条坑位速查)在 /learn/claude-code-cloud-pc,本文只取和企微直接相关的部分。
.env、查进程、拉起 / 结束计划任务status.json、算文件 md5InteractiveToken 跳到 Session 1py.exe 启动器在 SYSTEM 下起不来,要用完整路径或纯 PowerShellschtasks /run 返回 rc=0 ≠ 真起来了(见第 06 节最阴的那个坑)验证探针自己要先有存在性证据。用某条日志、某个字段当回执前,先拿一个「必然产生该信号」的动作跑一遍、确认看得见。否则「收不到信号」和「动作没发生」分不开——我们靠这条纪律把三条已判死的通道又救活了两条。
云电脑上常驻的就是一个 Python 文件(wecom_reply_loop.py,10 万字节出头)。它不做任何判断以外的智能工作——话术由服务端大脑生成,它只负责眼睛和手。
persona:显示名,读歪就会新建会话、历史清零,甚至发错人。发送路径上这一步是硬门
CROP_RATIO)、右裁新版客户端的 AI+ 侧栏(CROP_RIGHT_RATIO),裁错就会把自己的话或侧栏示例卡片当成客户消息
/api/wecom/chat/reply(经 Fly 中转)。服务端落客户消息 → 拼历史 + 滚动摘要 + 客户画像 → 生成回复 → 按触发词匹配官方海报 → 返回文本 + 图片 URL
ctypes SendInput 逐字 Unicode 打字 → 回车。粘贴模式试过,回滚了:文字进得去但不触发发送,卡成草稿 = 日志报「已发送」而客户收不到
confirm 落 AI 消息(wecom_msgid 幂等指纹,3 次重试——单次失败会导致下一轮重生成重发)
这条链的设计原则是「模型只做模糊判断,代码做一切确定性的事」:视觉模型负责「读出这句话」,气泡归属 / 会话路由 / 计数 / 限流 / 幂等 全部走确定性代码。早期让 VL 判「这条是客户还是我自己发的」,翻车了,改成确定性归属才收敛。
下面每一格都对应真实的合并记录或线上验证。看得出一个规律:前两个月在找路,中间两个月在治「回得对不对」,最近一个月全在治「这东西自己活不活得下去」。
effort:"low" 压不住 → 话术漂移 + 延迟。回滚 qwen3-235b-2507
context_summary,按水位增量消化、不受历史窗口限制
下面这张表的每一行都能从线上现查——脚本版本查 /wecom/loop-script/meta,配置版本查 /api/wecom/config-version。写这页时实拉过一次。
时延口径:不含客户实际发出到云电脑读屏那一段(轮询等待 + VL 读屏,测不到)。近 7 天单聊 51 个活跃会话 / 162 条客户消息 / 141 条 AI 回复,p90 88s。
| 层 | 现状 | 版本 / 取值 |
|---|---|---|
| 云电脑脚本 | 常驻主循环,自带自更新 | v2026-07-25.adaptive-selfupdate.2 md5 04a937bc · 103,487 B |
| 启动器 | 预启动同步 + 崩溃 10s 重拉 | start-wecom-reply.bat(机上 CRLF) |
| 看门狗 | SYSTEM 计划任务,每 5 分钟 | AkkeWecomLoopWatchdog |
| 话术模型 | 企微通道独立选型,可 env 灰度 | qwen/qwen3-235b-a22b-2507 |
| 视觉模型 | 读屏,经 Fly 中转出海 | qwen3-vl(OpenRouter) |
| 轮询节奏 | 活跃档与空闲档都压在 5–12s | MIN/MAX = ACTIVE_MIN/MAX = 5/12 |
| 发送限流 | 生产值(测试期曾放宽,已收口) | 日 200 · 时 50 · 单客户 100 |
| 召回独立日限 | 与被动回复分开计 | 5 |
| 机器 A | 人设 xiaogu(小顾) | ecd-5qfbmvz3yvpvbiog0 |
| 机器 B | 人设 xiawei(夏伟) | ecd-eq68qf5rxfcpvxrl2 |
| 坐标 | 每台必须实测,同分辨率也不能抄 | C_INPUT / C_SEND / C_SESSION1 CROP_RATIO / CROP_RIGHT_RATIO |
两台机器分辨率完全相同(2940×1684),坐标却完全不能互抄——企微客户端版本不同,左侧会话列表右缘从屏宽 21% 挪到 34.7%。同理,新版客户端多出来的「AI+」侧栏里有一张虚构客户的示例卡片,不裁掉右边界,视觉模型会把它当成真实客户消息读进来。
7 月之前,改一行 loop 代码要人肉传两台机器——而 GitHub raw、jsDelivr、paste.rs 在这台机器上全部被墙或扛不住体积。7 月 25 日这条链改成自动的,代价是逼出了三个原本看不见的 bug。
后端把 loop 脚本本身做成发布产物:GET /wecom/loop-script 给文件,/meta 给 md5 + 版本 + 字节数。loop 每 20 轮比对一次远端 md5,不一致就干净退出,让 launcher 在重启前先同步再拉起。全程无人工。
整条链死掉时(黑窗被关、cmd host 崩)此前唯一兜底是重启机器。现在一个 SYSTEM 计划任务每 5 分钟查一次,三重守卫缺一不可:有 loop 进程 / 有停机锁 / 有 launcher host 进程——任一命中就不动。少一个守卫就会起出第二个 loop,两个 loop 抢同一个窗口 = 重复回复客户。
发布端根本不会重新部署。部署 workflow 的 paths-ignore 里有 cloudpc/**——而被发布的脚本正是那个目录下的文件,只改 loop 的提交不触发部署,端点发出去的永远是旧字节。症状极隐蔽:CI 绿、PR 合了、meta 端点 200,就是 md5 一直不变。
教训:改了一个目录的「性质」(从人工拷贝物 → 构建发布产物),要回头查所有按路径过滤的 workflow。
launcher 的同步一次超时就丢掉整轮更新:103 KB 穿墙下载有约 1/3 概率超 30 秒,而挂掉那次恰好是要装新版那次 → 「检测到新版 → 退出 → 同步失败 → 旧版重启 → 20 轮后再来一遍」的空转,每圈赔 40 秒停机且永远装不上。改成 meta 3×25s / 下载 3×120s,每次都 md5 校验。
schtasks /run 返回 rc=0,任务动作却瞬间死掉、零输出。真因:任务动作是 cmd /c "... >> _loop.log 2>&1",而刚被杀掉的上一个实例通常还要 4–8 秒才释放日志文件句柄 → 追加重定向建不起来 → cmd 立刻 exit 1;而错误信息本身要走那个失败的重定向,所以哪儿都看不到。
修法是拉起前用同一个操作探测日志可写(最多等 60 秒)。推广:凡是「重启一个把 stdout 追加重定向到固定日志的常驻进程」都有这个坑,验收一律用进程存在性,别信返回码。
代回天天在改,但改动散在五层,各层版本号互不联合。后果是:「prompt v3」在上周和今天不是同一个行为——回滚 prompt ≠ 回滚行为,事后复盘也说不清那天到底什么配置。
| 配置层 | 改了之后 | 原本有版本吗 |
|---|---|---|
| Langfuse 话术 | 后台一改立刻生效 | 有 version,但不发版、不过任何 CI |
| 代码常量 | 随部署生效 | 只有 commit |
| 模型 env | 改完 redeploy 生效 | 连部署记录都没有 |
| 云电脑 loop 脚本 | 自愈拉取后生效 | 手写 VERSION 字符串 |
| 数据库语料 | 改行即生效 | 直接改,无历史 |
wc-YYYY.MM.DD-xxxxxx/api/wecom/config-version)--at / --diff)GUI 那半边解决「发得出去」,服务端这半边解决「说得对不对」。这里的每一项都是被真实翻车逼出来的,不是设计时想到的。
三条病根:历史压缩超 16 条只留最近 10 条且只扫我方消息(客户说的城市/面积/预算全丢);画像靠模型自愿调工具(59 条会话只写了 4 条);路由历史窗口 40 条封顶。
修法:确定性抽取 7 字段 + 滚动摘要按水位增量消化,异步落库不占 GUI 等待。
对「拿了资料就不回」的客户:按画像从甲方话术库选 7 个话题冻结成计划,前 3 天每天 1 条、之后退避隔天,客户回复 / 被删 / 满 7 次出库。
护栏对齐平台风控:官方建议 ≤1 条/天、消息相似度 >70% 会被限流,所以有专门的防复读指令。
此前全仓零度量,日报报的是加好友数。现在有确定性「约成到店」判据(必须有明确时间锚点或应约,「哪天过去方便」不算)+ 每天推五档漏斗。
造出来才发现真正的漏点在最前面:46 条像真客户的会话里,33 条客户开口 ≤2 句就没下文。
企微通道独立于抖音选型,可各自灰度。7-15 复评切 kimi-k2.6(更短、无编造门店),7-21 回滚——它是推理模型,生产只传了低档 effort 压不住,话术漂移 + 延迟。
回滚成本为零:改一个 env 就行,代码默认不动。
转人工闸的判据是裸子串,全屋定制最常见的 9 句问句里有 5 句会触发永久闭麦(「人工费」「真人上门量尺」「找个人」…),而当时闭麦是终态、无路可回。
修成带上下文正则 + 否定放行 + 24 小时冷静期自动复活。
同一个组织下多个销售的客户混在一张表,只靠会话身份键的 persona 前缀区分。加一个操作员 = 建一行账号 + 云电脑 .env 填三个值,零代码改动。
踩过的坑:另一个看起来更自然的字段在这条通道上恒为 NULL,拿它筛候选会得到零结果。
写对外页最容易的失真是只讲修好的部分。下面这几条是今天仍然存在的缺口,都有实测数据。
这套东西很特殊,但里面有几条判据是通用的——尤其是「什么时候该让模型进决策环、什么时候一定别」。
| 维度 | 值得让模型驱动 | 一定别 |
|---|---|---|
| 操作错的代价 | 读数、整理文件,错了重来 | 发错 = 骚扰真客户 / 烧线索 / 限号 |
| 频率 | 一次性、低频、人工触发 | 高频、批量、无人值守 |
| 谁来点击 | 模型只做核身 / 分类 / 话术 | 模型驱动每一步点击 |
| 目标元素 | 大而独立(整窗、大按钮) | 小而密集(输入框、发送键) |
| 确定性要求 | 模糊判断(是不是目标 / 什么意图) | 需要可复现的路由 / 状态 / 计数 |
任一行落右列,就别做「模型遥控点击」那一版。混合情况就拆开:确定性部分走代码,模糊部分才交给模型——这正是这套系统最终的形态。
这条链上至少三个返回码骗过我们:schtasks /run 的 rc=0、远程命令的 InvocationStatus(网络故障期连必失败的脚本都报 Success)、以及看门狗 v1 的空转。验收一律用进程存在性或业务指标——数据库里有没有真的多出一行。
「grep 得 0」「没收到回执」这类结论,必须先证明该通道在正例下会产生信号。我们靠一个未校准的日志通道把「远程命令根本不执行」写进过文档,次日复测全是正常的——假失败和假成功一样贵。
视觉读屏出错时,第一反应往往是改提示词或换模型。实际上这套系统里绝大多数读屏事故的根因都是几何的——裁剪比例把标题、客户气泡或侧栏切进/切出了。先量像素,再谈模型。
50 轮记忆、决策期话术、转人工——三个精心做的能力,真实触发次数都是 0。造之前先问「现在的对话能走到那一步吗」,比造完再找场景便宜得多。先做度量,再做能力。