Commit 7f20b54e3662936a3b67f563d3ba45fa9c45e7b9
1 parent
48d37f79
feat(wechat-auto-reply): 群回复自动 @ 触发者(文字前缀)
- process_event 里确定性拼接 @昵称+U+2005(微信原生分隔符)前缀,仅群消息(isGroup) - 确定性代码不靠 LLM/skill(否则会漏会不一致);安全判定仍在原始 draft 上跑 - 审批页/自动发的文本都带 @;送达校验用带 @ 的文本匹配 - ⚠️ 仅文字@(可见),不带 atuserlist,未必真 ping 红点;真通知需 hook 加 at 列表(README 待收尾) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Showing
2 changed files
with
17 additions
and
9 deletions
wechat_plain/scripts/auto-reply/README.md
| ... | ... | @@ -92,10 +92,11 @@ python wechat_reply_orchestrator.py --drain # 手动跑一轮编排 |
| 92 | 92 | |
| 93 | 93 | ## 待收尾(都非阻塞,系统已可用) |
| 94 | 94 | |
| 95 | -1. **原生引用气泡 / 群@昵称前缀**:现发纯文本;微信原生引用(A11y 长按→引用)是环节5 增强。 | |
| 96 | -2. **日志轮转**:watcher/orchestrator log 24/7 会长(ponytail 已标)。 | |
| 97 | -3. **lifelog 人物增强**(二期):回链 `com.lemon.lifelog` 的 Person 库让回复带人物记忆。 | |
| 98 | -4. ~~auto_send 开关搬进 viewer UI~~ ✅ **已做(2026-07-12)**:`/whitelist` 页顶部开关卡片,`GET/POST /api/auto_send` 读写 `policy_config.json`。 | |
| 95 | +1. **群回复 @ 触发者**:✅ **文字前缀已做(2026-07-12)**——群消息回复自动加 `@昵称 `(U+2005=微信原生分隔符),`process_event` 里确定性拼接(不靠 LLM)。⚠️ 仅**文字 @**(看得见),**不带 `atuserlist`** → 未必真 ping 对方红点「有人@我」;真通知要给 `SEND_MSG_INTERNAL` hook 加 at 列表 + 逆向微信发送 API 的 at 字段(大工程,待定)。 | |
| 96 | +2. **原生引用气泡**:现发纯文本;微信原生引用(A11y 长按→引用)是环节5 增强。 | |
| 97 | +3. **日志轮转**:watcher/orchestrator log 24/7 会长(ponytail 已标)。 | |
| 98 | +4. **lifelog 人物增强**(二期):回链 `com.lemon.lifelog` 的 Person 库让回复带人物记忆。 | |
| 99 | +5. ~~auto_send 开关搬进 viewer UI~~ ✅ **已做(2026-07-12)**:`/whitelist` 页顶部开关卡片,`GET/POST /api/auto_send` 读写 `policy_config.json`。 | |
| 99 | 100 | |
| 100 | 101 | ## 相关文档 |
| 101 | 102 | - `../../../md/wechat-auto-reply-requirements.md` — 需求 + 逐环节设计/状态 | ... | ... |
wechat_plain/scripts/auto-reply/wechat_reply_orchestrator.py
| ... | ... | @@ -267,16 +267,23 @@ def process_event(event, stage, key, self_wxid, cfg): |
| 267 | 267 | if action == "skip": |
| 268 | 268 | log(f"skip #{event.get('msgId')} {sender_disp}: {reason}") |
| 269 | 269 | return |
| 270 | + # 群里回复自动 @ 触发者:昵称前缀 + U+2005(微信原生 @ 分隔符)。安全判定(decide)已在 | |
| 271 | + # 原始 draft 上跑过,前缀只影响发送/展示的文本。 | |
| 272 | + # ponytail: 只是文字 @(看得见),不带 atuserlist → 未必真 ping 到对方红点;真通知要改 | |
| 273 | + # SEND_MSG_INTERNAL hook 加 at 列表(见 README 环节5 待收尾)。 | |
| 274 | + send_text = draft | |
| 275 | + if event.get("isGroup") and sender_disp: | |
| 276 | + send_text = f"@{sender_disp} {draft}" | |
| 270 | 277 | if action == "auto": |
| 271 | - res = _send_text_verified(event.get("talker"), draft, key) | |
| 278 | + res = _send_text_verified(event.get("talker"), send_text, key) | |
| 272 | 279 | ok = res["ok"] |
| 273 | - _queue_insert(event, draft, sender_disp, "auto", | |
| 280 | + _queue_insert(event, send_text, sender_disp, "auto", | |
| 274 | 281 | reason, "auto_sent" if ok else "failed") |
| 275 | 282 | log(f"auto {'OK ' + res['reason'] if ok else 'FAILED ' + res['reason']} " |
| 276 | - f"#{event.get('msgId')} {sender_disp} → {draft!r}") | |
| 283 | + f"#{event.get('msgId')} {sender_disp} → {send_text!r}") | |
| 277 | 284 | else: # review |
| 278 | - _queue_insert(event, draft, sender_disp, "review", reason, "pending") | |
| 279 | - log(f"review #{event.get('msgId')} {sender_disp}: {reason} → 草稿 {draft!r}") | |
| 285 | + _queue_insert(event, send_text, sender_disp, "review", reason, "pending") | |
| 286 | + log(f"review #{event.get('msgId')} {sender_disp}: {reason} → 草稿 {send_text!r}") | |
| 280 | 287 | |
| 281 | 288 | |
| 282 | 289 | # ── drain:消费 incoming.jsonl ───────────────────────── | ... | ... |