diff --git a/wechat_plain/scripts/auto-reply/README.md b/wechat_plain/scripts/auto-reply/README.md index cd93d1b..69fcd13 100644 --- a/wechat_plain/scripts/auto-reply/README.md +++ b/wechat_plain/scripts/auto-reply/README.md @@ -92,10 +92,11 @@ python wechat_reply_orchestrator.py --drain # 手动跑一轮编排 ## 待收尾(都非阻塞,系统已可用) -1. **原生引用气泡 / 群@昵称前缀**:现发纯文本;微信原生引用(A11y 长按→引用)是环节5 增强。 -2. **日志轮转**:watcher/orchestrator log 24/7 会长(ponytail 已标)。 -3. **lifelog 人物增强**(二期):回链 `com.lemon.lifelog` 的 Person 库让回复带人物记忆。 -4. ~~auto_send 开关搬进 viewer UI~~ ✅ **已做(2026-07-12)**:`/whitelist` 页顶部开关卡片,`GET/POST /api/auto_send` 读写 `policy_config.json`。 +1. **群回复 @ 触发者**:✅ **文字前缀已做(2026-07-12)**——群消息回复自动加 `@昵称 `(U+2005=微信原生分隔符),`process_event` 里确定性拼接(不靠 LLM)。⚠️ 仅**文字 @**(看得见),**不带 `atuserlist`** → 未必真 ping 对方红点「有人@我」;真通知要给 `SEND_MSG_INTERNAL` hook 加 at 列表 + 逆向微信发送 API 的 at 字段(大工程,待定)。 +2. **原生引用气泡**:现发纯文本;微信原生引用(A11y 长按→引用)是环节5 增强。 +3. **日志轮转**:watcher/orchestrator log 24/7 会长(ponytail 已标)。 +4. **lifelog 人物增强**(二期):回链 `com.lemon.lifelog` 的 Person 库让回复带人物记忆。 +5. ~~auto_send 开关搬进 viewer UI~~ ✅ **已做(2026-07-12)**:`/whitelist` 页顶部开关卡片,`GET/POST /api/auto_send` 读写 `policy_config.json`。 ## 相关文档 - `../../../md/wechat-auto-reply-requirements.md` — 需求 + 逐环节设计/状态 diff --git a/wechat_plain/scripts/auto-reply/wechat_reply_orchestrator.py b/wechat_plain/scripts/auto-reply/wechat_reply_orchestrator.py index 455c088..db07e38 100644 --- a/wechat_plain/scripts/auto-reply/wechat_reply_orchestrator.py +++ b/wechat_plain/scripts/auto-reply/wechat_reply_orchestrator.py @@ -267,16 +267,23 @@ def process_event(event, stage, key, self_wxid, cfg): if action == "skip": log(f"skip #{event.get('msgId')} {sender_disp}: {reason}") return + # 群里回复自动 @ 触发者:昵称前缀 + U+2005(微信原生 @ 分隔符)。安全判定(decide)已在 + # 原始 draft 上跑过,前缀只影响发送/展示的文本。 + # ponytail: 只是文字 @(看得见),不带 atuserlist → 未必真 ping 到对方红点;真通知要改 + # SEND_MSG_INTERNAL hook 加 at 列表(见 README 环节5 待收尾)。 + send_text = draft + if event.get("isGroup") and sender_disp: + send_text = f"@{sender_disp} {draft}" if action == "auto": - res = _send_text_verified(event.get("talker"), draft, key) + res = _send_text_verified(event.get("talker"), send_text, key) ok = res["ok"] - _queue_insert(event, draft, sender_disp, "auto", + _queue_insert(event, send_text, sender_disp, "auto", reason, "auto_sent" if ok else "failed") log(f"auto {'OK ' + res['reason'] if ok else 'FAILED ' + res['reason']} " - f"#{event.get('msgId')} {sender_disp} → {draft!r}") + f"#{event.get('msgId')} {sender_disp} → {send_text!r}") else: # review - _queue_insert(event, draft, sender_disp, "review", reason, "pending") - log(f"review #{event.get('msgId')} {sender_disp}: {reason} → 草稿 {draft!r}") + _queue_insert(event, send_text, sender_disp, "review", reason, "pending") + log(f"review #{event.get('msgId')} {sender_disp}: {reason} → 草稿 {send_text!r}") # ── drain:消费 incoming.jsonl ─────────────────────────