v0.2.0: add Linux server resident-agent route + ops-workflow discipline
- scripts/agent.py: GLM-vision WeChat guard agent (8s watch loop, red-dot detection, whitelist, title-verify gate, rate/quiet-hours caps, self-heal) - scripts/setup-server.sh: idempotent xfce+TigerVNC+noVNC+WeChat-Linux setup - scripts/check-skill.sh: skill guard (structure/secrets/privacy/claims/ encoding/syntax/CHANGELOG/bundle parity), self-exclusion aware - references/linux-server.md: deploy & ops guide for the 7x24 route - references/pitfalls-linux.md: 17 field-tested pitfalls - references/ops-workflow.md: baseline-first authoring + sync discipline (methodology credit: octopus-workflow) - SKILL.md/README: dual-platform entry; CHANGELOG added
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
# Linux 云服务器路线:7x24 挂机的微信 Agent
|
||||
|
||||
> macOS 路线(vision.py)操作你眼前的电脑;本路线把微信搬上云服务器虚拟屏,
|
||||
> 配一个 GLM 视觉 Agent 常驻值守——人关机,微信不下线。已在 2核2G Ubuntu 24.04 实战。
|
||||
|
||||
## 与 macOS 路线的哲学分工
|
||||
|
||||
| | macOS(vision.py) | Linux 服务器(agent.py) |
|
||||
|---|---|---|
|
||||
| 坐标性质 | **易耗品**:每次实时定位,绝不落盘 | **固定资产**:虚拟屏分辨率固定+窗口最大化固定,坐标一次校准长期有效 |
|
||||
| 会话生命周期 | 跟随你的电脑 | 持久虚拟屏,断开连接微信照常在线 |
|
||||
| 决策 | 人(你)实时判断 | GLM 视觉模型+规则闸门 |
|
||||
| 适用 | 当下的一次性界面操作 | 长期自动值守 |
|
||||
|
||||
为什么 Linux 侧可以写死坐标:虚拟屏 `:1` 分辨率永远是 1440x900,微信窗口被 wmctrl
|
||||
强制最大化——布局漂移的根源(真实桌面)不存在。**一旦换了分辨率,全部坐标重新校准**,
|
||||
校准方法见下文。
|
||||
|
||||
## 架构
|
||||
|
||||
```
|
||||
xfce4 桌面 + TigerVNC 虚拟屏 :1 (仅监听 localhost)
|
||||
└─ 微信官方 Linux 版(常驻)
|
||||
└─ agent.py (systemd 常驻)
|
||||
├─ 每 8 秒 scrot 截屏 → PIL 红点像素检测(本地,零成本)
|
||||
├─ 白名单过滤 → GLM 视觉读会话 → GLM 生成回复
|
||||
├─ 发送前视觉核验窗口标题(防发错人的最后闸门)
|
||||
└─ xdotool 点击 / xclip 剪贴板粘贴中文
|
||||
noVNC(websockify) ← SSH 隧道 ← 你的浏览器(看它干活)
|
||||
```
|
||||
|
||||
不 hook 协议、不逆向,纯 GUI 自动化,封号风险最低。
|
||||
|
||||
## 快速部署
|
||||
|
||||
```bash
|
||||
# 1. 环境搭建(幂等,装 xfce+TigerVNC+noVNC+中文字体+swap+微信,约10分钟)
|
||||
scp scripts/setup-server.sh root@<服务器IP>:/root/
|
||||
ssh root@<服务器IP> "bash setup-server.sh" # 末尾按提示 vncpasswd 设密码
|
||||
|
||||
# 2. 本机建隧道,浏览器开 http://localhost:6080/vnc.html 扫码登录
|
||||
ssh -L 6080:localhost:6080 root@<服务器IP>
|
||||
|
||||
# 3. 部署 Agent
|
||||
ssh root@<服务器IP> "mkdir -p /opt/wechat-agent"
|
||||
scp scripts/agent.py scripts/config-template.json root@<服务器IP>:/opt/wechat-agent/
|
||||
scp scripts/wechat-agent.service root@<服务器IP>:/etc/systemd/system/
|
||||
# 编辑 /opt/wechat-agent/config.json:填 bigmodel API key、改白名单
|
||||
ssh root@<服务器IP> "systemctl daemon-reload && systemctl enable --now wechat-agent"
|
||||
```
|
||||
|
||||
模型:bigmodel 免费档够用(`text: glm-4.5-flash` + `vision: glm-4v-flash`);
|
||||
付费档(glm-5.3-flash 等)需资源包,拿到 key 先逐模型探测再写配置。
|
||||
|
||||
## 三档模式(先 draft 后 auto)
|
||||
|
||||
- `observe`:只记录新消息到 inbox.log,零操作
|
||||
- `draft`:GLM 拟回复写 inbox.log,**不发送**——人工审一天看质量
|
||||
- `auto`:全自动发送
|
||||
|
||||
安全闸门(全部固化在代码里,不是散文):发送前视觉核验窗口标题(空标题一律拒绝)、
|
||||
白名单外零操作(不点击不标已读)、≤10条/时、23:00-7:00 静默、转账/密码类只回
|
||||
"记下来了"、`touch /opt/wechat-agent/PAUSE` 一秒急停。
|
||||
|
||||
## 坐标校准(换分辨率时)
|
||||
|
||||
1. `wmctrl -r Weixin -b add,maximized_vert,maximized_horz` 最大化
|
||||
2. `scrot` 截图,人工读图量出:搜索框、列表槽位(y0/间距)、图标列、输入框、标题栏
|
||||
3. 改 config.json 的 coords → `python3 agent.py once` 验证红点检测
|
||||
4. **永远先拿 File Transfer 当发送链路的小白鼠**,不拿真人会话试
|
||||
|
||||
## 运维速查
|
||||
|
||||
```bash
|
||||
tail -f /opt/wechat-agent/inbox.log # 消息流和草稿
|
||||
python3 /opt/wechat-agent/agent.py once # 单次检测
|
||||
python3 /opt/wechat-agent/agent.py send "联系人" "内容" # 定向发送(带核验)
|
||||
touch /opt/wechat-agent/PAUSE && rm /opt/wechat-agent/PAUSE # 暂停/恢复
|
||||
systemctl stop wechat-agent # 彻底停机
|
||||
```
|
||||
|
||||
## 已知坑
|
||||
|
||||
17 条实战踩坑实录见 [pitfalls-linux.md](pitfalls-linux.md),高频三则:
|
||||
xclip 会挂起 ssh 会话、pkill 模式会匹配脚本自身、微信窗口隐藏后 wmctrl 仍能看到它
|
||||
(恢复逻辑必须无条件激活,不能以"列表里没有"为条件)。
|
||||
|
||||
## 风险与边界
|
||||
|
||||
个人微信自动化违反微信用户协议,有封号风险:控制频率、建议小号先行、
|
||||
本仓库所有安全闸门不建议放宽。扫码前确认手机当前登录的是**目标账号**
|
||||
——桌面版可切换多账号,登错号是真实发生过的事故(见 pitfalls 第17条)。
|
||||
@@ -0,0 +1,74 @@
|
||||
# skill 建设与维护工作流(ops-workflow)
|
||||
|
||||
> 本仓库自身的维护纪律,方法论源:[octopus-workflow](https://eightarms.net/Octopus/octopus-workflow)
|
||||
> (provider-neutral corpus + drift guards),经实战校准。适用于本仓库以及你按本模式自建的一切 skill。
|
||||
|
||||
## 铁律:没有失败基线,就没有行为塑造型 skill
|
||||
|
||||
写 skill 就是给流程文档做 TDD:
|
||||
|
||||
1. **RED 基线**——先看真实失败。最好的人肉素材是你自己踩过的事故:逐字记录当时怎么错的、用了什么借口、什么压力触发的。禁止对着想象中的失败写规则。
|
||||
2. **GREEN 最小化**——只针对观察到的问题写内容。
|
||||
3. **REFACTOR**——实测发现新绕过方式,补条目,循环到堵住。
|
||||
|
||||
## 分类决定投入
|
||||
|
||||
| 类型 | 例 | 基线要求 |
|
||||
|---|---|---|
|
||||
| 行为塑造型(压力下必须守住闸门) | "发送前必须核验窗口标题" | 必须有失败基线 |
|
||||
| 技术/方法型 | 截图→识别→动作→验证 闭环 | 场景验证即可 |
|
||||
| 参考/机械型 | 命令速查、依赖清单 | 保真检查+守卫 |
|
||||
| 实战移植 | 本仓库的踩坑实录 | 对照事故记录保真 |
|
||||
|
||||
## 形式匹配失败类型(选错形式会适得其反)
|
||||
|
||||
| 基线失败 | 正确形式 | 错误形式 |
|
||||
|---|---|---|
|
||||
| 压力下违反规则 | 禁令+借口对照表+红旗清单 | 软 guidance("建议/尽量") |
|
||||
| 输出形状不对 | 配方:说清输出是什么、按什么顺序 | 禁令清单 |
|
||||
| 漏了必做步骤 | 模板里的必填槽位 | 模板旁边的散文提醒 |
|
||||
| 行为依赖条件 | 绑定可观察谓词的条件式 | 无条件规则+豁免条款 |
|
||||
|
||||
**关键推论:能固化进代码的闸门,绝不放在散文里。** 代码不会在截止日期压力下和你讨价还价——本仓库的"发送前核验标题"最终是 `agent.py` 里的 `header_matches()` 函数,不是 SKILL.md 里的一行字。
|
||||
|
||||
## 守卫(guards):漂移靠工具不靠记忆
|
||||
|
||||
skill 建好后会存在于多处(源目录/安装目录/部署实例/分发包),漂移是常态。`scripts/check-skill.sh` 检查:
|
||||
|
||||
结构完整、引用文件存在、凭证泄露(HARD)、私密信息(SOFT)、未证实断言、编码健康、脚本语法、CHANGELOG、分发包一致性。
|
||||
|
||||
```bash
|
||||
bash scripts/check-skill.sh <skill目录> [--bundle <tar.gz>]
|
||||
```
|
||||
|
||||
已踩过的守卫设计坑:守卫自身的检测模式会被当成命中(模式样本伪装命中)——扫描前剥离行内代码段、排除 guards/ 目录;规则文档里合法引用的违例词,靠"剥离代码段后重测"区分。
|
||||
|
||||
## 三方同步纪律
|
||||
|
||||
```
|
||||
源目录(唯一编辑点) → 安装目录 → 分发包(永远重新打包,禁止改包)
|
||||
部署实例(如服务器 /opt/xxx)的改动必须先回填源目录,再走发布
|
||||
```
|
||||
|
||||
记忆/文档只存指针(源在哪、包在哪、版本号),不复述内容——复述必然过时。
|
||||
|
||||
## 证据分级表述
|
||||
|
||||
写"实战验证"前先回答:证据是什么?
|
||||
|
||||
- ✅ "已验证:File Transfer 心跳链路端到端送达(截图确认)"
|
||||
- ✅ "未验证:私聊自动回复的实际送达(审计截图缺失,待人工确认)"
|
||||
- ❌ "已跑通,消息真实送达"(含糊,且当时并未眼见确认)
|
||||
|
||||
## 复盘(事故后必做,无指责)
|
||||
|
||||
DATA(每条失误附证据)→ ANAL(归因流程/工具/信息缺口,不归因个人)→ ACT(≤5条,每条有文件路径和完成判据)→ LOOP(素材文件真的改了)→ SAFE(不指责任何人,含自己)。
|
||||
|
||||
## 本仓库的应用实例
|
||||
|
||||
v0.2.0 整合 Linux 服务器 Agent 时,以上流程实际这样走:
|
||||
|
||||
1. **基线**:三天实战中的真实事故(私聊消息发错群、空标题核验漏洞、二维码被自愈循环刷新)就是 RED 数据
|
||||
2. **GREEN**:每条事故固化为 pitfalls 一条 + 可能的话固化为 agent.py 的闸门函数
|
||||
3. **守卫**:check-skill.sh 在发布前抓出分发模板里的真实联系人名和未证实断言(也抓出守卫自身的假阳性,遂加"剥离代码段"规则)
|
||||
4. **同步**:服务器 /opt/wechat-agent 的 agent.py 与仓库 scripts/agent.py 做了 diff 一致性确认
|
||||
@@ -0,0 +1,89 @@
|
||||
# 踩坑实录(全部实战验证过的解法)
|
||||
|
||||
## 1. xclip 会挂起整个 ssh 会话
|
||||
|
||||
`xclip -selection clipboard` 设置剪贴板后会**驻留为剪贴板所有者**,占住 ssh 的 stdout 管道,远程命令永不返回,还会导致后续 cat 截图文件拿到 0 字节。
|
||||
|
||||
**解法**:setsid 分离 + 三重丢弃:
|
||||
|
||||
```bash
|
||||
setsid xclip -selection clipboard -i /tmp/clip.txt >/dev/null 2>&1 < /dev/null &
|
||||
```
|
||||
|
||||
## 2. pkill 的模式匹配会杀掉脚本自己
|
||||
|
||||
远端脚本里写 `pkill -f "xclip -selection"`,如果脚本自己的命令行里也含这个字样(bash -c 整段就是命令行),**脚本会杀掉自己**,exit 255,后面的命令全不执行。括号技巧 `pkill -f "[x]clip"` 只对 grep/pkill 自身有效,**保护不了内嵌在脚本命令行里的字符串**。
|
||||
|
||||
**解法**:需要结束时用 `pkill -x xclip`(精确进程名);脚本里尽量不写 pkill。
|
||||
|
||||
## 3. 微信窗口被藏进托盘
|
||||
|
||||
点击搜索下拉、Esc、某些误操作会把窗口最小化到托盘,`wmctrl -l` 里看不到 Weixin,但进程活着。此时截屏全是桌面壁纸,Agent 变瞎,还会把壁纸当界面乱点。
|
||||
|
||||
**解法**:每轮轮询前 ensure_window():`wmctrl -l` 无 Weixin 就 `xdotool search --class wechat` 拿窗口 id,`wmctrl -i -a <id>` 拉回,再重新最大化。agent.py 已内置。
|
||||
|
||||
## 4. "Hide pinned chats" 折叠开关
|
||||
|
||||
微信聊天列表底部有折叠置顶的开关。**点击列表底部区域(y>826)极易误触它**,把置顶会话全部折叠——File Transfer 这类内置会话通常在置顶区,折叠后就"消失"了。折叠后列表顶部会出现 "N pinned chats" 字样。
|
||||
|
||||
**解法**:① 图标定位的 y 搜索上限设 826,永不进入开关区;② 发送前检测折叠条(vision 问 (60,240)-(300,285) 区域有无 pinned chats 字样),有就点 (160,260) 展开;③ 点图标用图标列中心 x=91,不用行中心。
|
||||
|
||||
## 5. File Transfer(文件传输助手)搜不到
|
||||
|
||||
英文 UI 下文件传输助手显示为 "File Transfer",且**搜索框的本地索引不收录它**——搜什么只会出网络搜索建议,点建议还会把窗口搞进托盘。
|
||||
|
||||
**解法**:用像素特征在列表里直接找:File Transfer 图标是绿圈+大白箭头,36px 高的窗口内 绿>250 且 白>250 双特征,取最底部匹配。绿 (1,194,96),判定 `r<80 and g>160 and 60<b<140`。agent.py 的 `find_green_icon_y` 已实现。
|
||||
|
||||
## 6. 中文输入只能走剪贴板
|
||||
|
||||
`xdotool type "中文"` 依赖键盘映射,输出的全是乱码。**唯一可靠**的是剪贴板+ctrl+v(见 SKILL.md 代码块)。注意粘贴后 sleep 0.6-1.5 再动手,大文本渲染需要时间。
|
||||
|
||||
## 7. 视觉模型读标题的两类幻觉
|
||||
|
||||
- 提示词问"输出标题文字",模型看不到时会**编造**或输出解释性长文("标题是微信。");
|
||||
- 解法:提示词里给"看不到就输出NONE"的出口,代码侧再兜底:`结果含NONE或长度>20 视为空`。agent.py 的 chat_header_name 已实现。
|
||||
|
||||
## 8. UI 是英文的
|
||||
|
||||
官方 Linux 微信跟随系统 locale,Ubuntu Server 默认英文 → 界面全是 "Search"/"File Transfer"/"Send"。给视觉模型的提示词要用英文界面词;联系人名以**界面显示名**为准(用户嘴里的"文件传输助手"=界面上的"File Transfer")。想要中文界面,在 xstartup 里 `export LANG=zh_CN.UTF-8` 并装 language-pack-zh-hans。
|
||||
|
||||
## 9. 发消息点进群聊(最严重事故)
|
||||
|
||||
坐标偏移 + 无核验 = 私聊消息发进 227 人群。除了发送前标题核验,还有两条纪律:
|
||||
|
||||
- 新坐标/新流程**永远先拿 File Transfer 做小白鼠**,不拿真人会话试;
|
||||
- 视觉定位返回的坐标,点击后必须再截屏核验"现在开的是不是目标会话",一步都不省。
|
||||
|
||||
## 10. 2G 内存配置基准
|
||||
|
||||
xfce(300M) + 微信(500M-1G) + Agent(PIL+requests 约 30M):available 常态 500M 左右。free -h 的 available < 200M 时先查 evolution 系列有没有复活,再考虑升配。evolution-alarm-notify 会在 xfce 会话每次重启时试图复活,Hidden=true 覆盖文件必须常驻。
|
||||
|
||||
## 11. systemd 服务里拉起的微信秒退(HOME 缺失)
|
||||
|
||||
systemd 服务环境默认**没有 HOME**。从 agent 进程里 Popen 启动微信,微信因写不了 `~/.xwechat` 立即退出,表现为"拉起后 ps 里没有进程"。解法:service 单元加 `Environment=HOME=/root`,且 agent.py 里 ENV 显式补 HOME。
|
||||
|
||||
## 12. wmctrl -lG 列序:`[3]` 是 y 不是宽度
|
||||
|
||||
`wmctrl -lG` 输出为 `id desktop x y W H name`,取宽度必须用 `split()[4]`。取错列会把 y=27(最大化窗口)当成"27px 的小窗口",整个状态机判断全歪。
|
||||
|
||||
## 13. 窗口未映射 ≠ 进程死亡(过渡态绝不能杀)
|
||||
|
||||
刚登录成功/启动中的微信,主窗口有几秒未映射期(wmctrl -lG 里没有 Weixin,但 pgrep 有进程)。如果把这当"进程死了"去 pkill+重启,**会杀掉刚登录的会话,用户被迫重新手机确认**。判据:窗口缺失时先 `pgrep -x wechat`,进程活着就只等待+激活,永远不要在进程存活时走硬重启。
|
||||
|
||||
## 14. 登录二维码窗的绿字 vs Log In 按钮都是微信绿
|
||||
|
||||
二维码登录窗上 "Scan to log in" 是绿色文字,和实心 Log In 按钮同色系。按"绿像素总数"判断会误触发点击。可靠判据:**单行连续绿像素数**——按钮 ~170x33 实心块,中心行采样约 75+;文字行零散笔画 ≤30。阈值取 50。
|
||||
|
||||
## 15. 任何自愈动作前先问:会不会毁掉登录态/二维码
|
||||
|
||||
微信进程一旦重启:登录退化为二维码,用户必须重新扫码+手机确认。所以自愈逻辑的每一版都要过一遍"最坏情况审查":误重启的代价是什么?亮度阈值、窗口宽判据、进程存活判断,宁可保守(等待)也不激进(重启)。重启冷却 90 秒是底线,不是保险。
|
||||
|
||||
## 16. 视觉模型会照抄提示词里的占位符
|
||||
|
||||
glm-4v-flash 对"只输出JSON: {chat_name:'名字', text:'...'}"这类带占位示例的提示词,可能**原样返回占位符**而不是读图(尤其图里没有聊天窗时)。对策:示例值用明显的假内容,代码侧校验(文本长度≥2 且不等于 "..."),不合规按读取失败处理。
|
||||
|
||||
## 17. 多账号陷阱:扫的码不一定是目标账号
|
||||
|
||||
桌面版微信记住了账号A,但登录页可 Switch Account;用户重新扫码时可能扫成**另一个账号B**。后果:聊天列表全变、白名单联系人"消失"(搜索只剩 Internet search results)。agent 表面正常,实际监护着错误的身份。
|
||||
|
||||
**对策**:①登录后第一件事,核对左上角头像/特征会话是否与白名单匹配;②白名单联系人在当前账号不存在时,搜索只剩网络建议——这是换错号的强信号;③部署时明确告知用户"扫码前确认手机当前登录的是哪个号"。
|
||||
Reference in New Issue
Block a user