from pathlib import Path import shutil ROOTS = [Path('C:/wechat_rpa'), Path('C:/kefu/wechat_rpa')] def replace_once(source, before, after): assert source.count(before) == 1, (before[:100], source.count(before)) return source.replace(before, after) for root in ROOTS: target = root / 'wechat_bot.py' original = target.read_bytes() source = original.decode('utf-8').replace('\r\n', '\n') source = replace_once(source, ' value.get("confirmed_unread", False)\n or str(value.get("send_state") or "")\n', ' value.get("confirmed_unread", False)\n or value.get("awaiting_review", False)\n or value.get("review_approved", False)\n or str(value.get("stage") or "") == "manual_review"\n or str(value.get("send_state") or "")\n') marker = ' "awaiting_review": bool(value.get("awaiting_review", False)),\n' assert source.count(marker) == 2 source = source.replace(marker, marker + ''' "review_reason": str(value.get("review_reason") or "")[:500], "review_origin": str(value.get("review_origin") or "")[:16], "review_approved": bool(value.get("review_approved", False)), "review_approved_at": value.get("review_approved_at", 0.0), "judge_risk": str(value.get("judge_risk") or "")[:32], "judge_reason": str(value.get("judge_reason") or "")[:500], ''') source = replace_once(source, ' if send_baseline_blocks is not None:\n', ''' # Older clients discarded review reasons. Treat an unclassified # restored draft as still requiring a decision, never as permission # to send merely because the global mode is now automatic. if state["awaiting_review"] or ( state["stage"] == "manual_review" and not state["review_approved"] ): state["awaiting_review"] = True state["stage"] = "manual_review" if not state["review_reason"] and state["review_origin"] != "global": legacy_detail = str(value.get("stage_detail") or "") state["review_reason"] = ( legacy_detail[:500] if "命中审核规则" in legacy_detail or "裁判" in legacy_detail else "历史审核原因未保存,请人工核对本条草稿" ) if send_baseline_blocks is not None: ''') source = replace_once(source, ''' if self._reply_awaiting_review(fp): self._set_task_stage( fp, "manual_review", detail="AI 回复已填入输入框,等待人工核对并发送", ) self._log_queue_event(fp, "待审核", "回复草稿已填入企业微信输入框") return ''', ''' if self._reply_awaiting_review(fp): state = self._pending_reply_state(fp) or {} reason = str(state.get("review_reason") or "").strip() detail = ( f"{reason},已停止自动发送,等待人工核对并发送" if reason else "AI 回复已填入输入框,等待人工核对并发送" ) self._set_task_stage(fp, "manual_review", detail=detail) self._log_queue_event(fp, "待审核", detail) return ''') source = replace_once(source, ' state.pop("review_reason", None)\n', ''' state.pop("review_reason", None) state.pop("review_origin", None) state.pop("judge_risk", None) state.pop("judge_reason", None) state.pop("judge_score", None) state.pop("model_chosen", None) state.pop("model_candidates", None) ''') source = replace_once(source, ' pending_state["review_reason"] = rule_reason\n', ' pending_state["review_reason"] = rule_reason\n' ' pending_state["review_origin"] = "global" if blanket_review else "rule"\n') backup = root / 'backups/review-visibility-20260916' backup.mkdir(parents=True, exist_ok=True) assert not (backup / target.name).exists(), 'Backup already exists; inspect before rerun' shutil.copy2(target, backup / target.name) newline = '\r\n' if b'\r\n' in original else '\n' target.write_bytes(source.replace('\n', newline).encode('utf-8')) print(root, 'patched visual review persistence and reasons')