from pathlib import Path import os artifact = Path(r'C:\kefu\deploy\review-visibility-20260916') review = (artifact / 'review_state.py').read_text(encoding='utf-8') review = review.replace("delivery_uncertain = send_state == 'uncertain' or stage == 'uncertain'", "delivery_uncertain = send_state in {'uncertain', 'sent_uncommitted', 'unknown'} or stage in {'uncertain', 'receipt_check', 'unknown'}") review = review.replace("protected = send_state in {'sending', 'uncertain', 'sent_uncommitted', 'failed'} or stage in {'sending', 'receipt_check'}", "protected = delivery_uncertain or send_state in {'sending', 'failed'} or stage == 'sending'") review = review.replace("and not approved\n", "and not approved and not protected\n") review = review.replace(" if delivery_uncertain:\n", " if send_state == 'sent_uncommitted':\n reason = '回复已发送但归档尚未完成。请先在企业微信核对发送结果,再转交人工;保留发送记录,不会自动重发。' + ('\\n' + reason if reason else '')\n elif delivery_uncertain:\n") (artifact / 'review_state.py').write_text(review, encoding='utf-8') method = ''' def _handoff_queue_tasks(self, keys) -> None: requested = list(dict.fromkeys(str(key or "").strip() for key in (keys or []) if str(key or "").strip())) if not requested: return def confirm_receipt(): return QMessageBox.question( self, "核对发送结果后转人工", "这条回复可能已经发出。请先在企业微信的原会话核对发送结果。\\n\\n" "继续后将从自动队列移出,由人工跟进;保留发送记录,不会自动重发,也不会标记为已确认发送。\\n\\n" "已核对,确认转人工处理?", QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, QMessageBox.StandardButton.No, ) == QMessageBox.StandardButton.Yes def handoff(targets, confirmed): thread = getattr(self, "_thread", None) if thread is not None and thread.is_alive(): return thread.handoff_pending_tasks(targets, confirm_uncertain=confirmed) from gui_runtime import handoff_pending_reply_file path = OFFLINE_QUEUE_PATH return handoff_pending_reply_file(targets, str(path), confirm_uncertain=confirmed) pending = getattr(self.queue_page, "pending_states", {}) or {} confirmed = any(task_review_state(pending.get(key))["deliveryUncertain"] for key in requested) if confirmed and not confirm_receipt(): return try: result = handoff(requested, confirmed) # The worker may have started receipt checking after the UI snapshot. # Re-check at the backend and never treat a stale UI as confirmation. needs_confirmation = list(result.get("requires_confirmation") or []) if needs_confirmation: result["protected"] = [key for key in result.get("protected", []) if key not in needs_confirmation] if confirm_receipt(): followup = handoff(needs_confirmation, True) for field in ("handed_off", "deleted", "scheduled", "protected"): result[field] = sorted(set(result.get(field) or []) | set(followup.get(field) or [])) if followup.get("error"): result["error"] = followup["error"] except Exception as exc: QMessageBox.warning(self, "转人工失败", str(exc)) return if result.get("error"): QMessageBox.warning(self, "转人工失败", str(result["error"])) accepted = set(result.get("handed_off") or result.get("deleted") or []) scheduled = set(result.get("scheduled") or []) if accepted: self.append_log(f"已将 {len(accepted)} 条任务转交人工,保留发送记录,不再自动重发。", "notify") if scheduled: self.append_log(f"已登记 {len(scheduled)} 条人工接管操作,监听线程就绪后执行。", "notify") if result.get("protected"): QMessageBox.information(self, "暂不能转人工", "任务正在发送或属于其他账号,请等待发送结束并在对应账号下处理。") self.queue_page.refresh_data() self._schedule_console_push(urgent=True) ''' for root in (Path(r'C:\kefu\wechat_rpa'), Path(r'C:\wechat_rpa')): def save(name, content): path = root / name tmp = path.with_suffix('.audit-tmp') tmp.write_text(content, encoding='utf-8') os.replace(tmp, path) save('review_state.py', review) path = root / 'wechat_gui_qt.py' text = path.read_text(encoding='utf-8') start = text.index(' def _handoff_queue_tasks(self, keys) -> None:') end = text.index(' def _approve_queue_tasks(self, keys) -> None:', start) queue = 'queue_path_for_transport(self.runtime_settings.get("reply_transport", VISUAL), root=SCRIPT_DIR)' if root == Path(r'C:\wechat_rpa') else 'SCRIPT_DIR / "pending_replies.json"' text = text[:start] + method.replace('OFFLINE_QUEUE_PATH', queue) + text[end:] text = text.replace('if send_state == "sending":\n return "正在发送"\n if review["deliveryUncertain"]:', 'if send_state == "sending" or state.get("stage") == "sending":\n return "正在发送"\n if send_state == "sent_uncommitted":\n return "已发送,待归档(需人工核对)"\n if review["deliveryUncertain"]:') text = text.replace(' if send_state == "sent_uncommitted":\n return "已发送,待归档"\n', '') save('wechat_gui_qt.py', text) for name in ('test_desktop_review_visibility.py', 'test_qt_console_bridge.py'): text = (root / name).read_text(encoding='utf-8') text = text.replace("thread.handoff_pending_tasks.assert_called_once_with(['uncertain'])", "thread.handoff_pending_tasks.assert_called_once_with(['uncertain'], confirm_uncertain=True)") text = text.replace('thread.handoff_pending_tasks.assert_called_once_with(["done", "protected"])', 'thread.handoff_pending_tasks.assert_called_once_with(["done", "protected"], confirm_uncertain=False)') if name == 'test_desktop_review_visibility.py': text += ''' def test_stale_snapshot_requires_explicit_confirmation_before_backend_handoff(self): self.seed() thread=SimpleNamespace(is_alive=lambda:True, handoff_pending_tasks=mock.Mock(side_effect=[ {'handed_off':[], 'protected':['ordinary'], 'requires_confirmation':['ordinary']}, {'handed_off':['ordinary'], 'protected':[]}, ])) self.host._thread=thread with mock.patch.object(QMessageBox,'question',return_value=QMessageBox.StandardButton.Yes) as question: qt.MainWindow._handoff_queue_tasks(self.host,['ordinary']) self.assertEqual(thread.handoff_pending_tasks.call_args_list,[ mock.call(['ordinary'],confirm_uncertain=False), mock.call(['ordinary'],confirm_uncertain=True)]) question.assert_called_once() def test_legacy_receipt_states_are_visible_but_not_sendable(self): for state in ({'stage':'receipt_check'}, {'send_state':'unknown'}, {'send_state':'sent_uncommitted'}): with self.subTest(state=state): flags=task_review_state(state) self.assertTrue(flags['deliveryUncertain']);self.assertTrue(flags['needsHuman']) self.assertFalse(flags['canApprove']);self.assertFalse(flags['canRetry']) self.assertEqual(self.page._display_task_status(self.page._state_text(state)),'需人工') ''' save(name, text) print(str(root) + ': receipt confirmation strengthened')