Files
kefu/deploy/review-visibility-20260916/handoff_patch.py
T
2026-09-21 10:34:06 +08:00

125 lines
6.8 KiB
Python

from pathlib import Path
import shutil
protocol = Path('C:/wechat_rpa/protocol_engine.py')
text = protocol.read_text(encoding='utf-8')
old = " def cancel_pending_replies(self,keys):return self._control(keys,'delete')"
assert text.count(old) == 1
text = text.replace(old, ''' def handoff_pending_replies(self, keys):
"""Explicitly retire selected work to a human without claiming delivery.
Uncertain receipts remain in the send journal and the inbound dedup key
stays seen. No send/retry is scheduled by this action.
"""
result={'handed_off':[], 'deleted':[], 'missing':[], 'protected':[], 'scheduled':[]}
removed=[]
with self._lock:
for key in dict.fromkeys(str(k).strip() for k in keys if str(k).strip()):
state=self._pending.get(key)
if state is None:result['missing'].append(key);continue
if state.get('account')!=self.account or state.get('send_state')=='sending':
result['protected'].append(key);continue
if state.get('dedup_key'):self._seen[key]=state['dedup_key']
self._pending.pop(key,None)
result['handed_off'].append(key);result['deleted'].append(key)
removed.append((key,state))
self.needs_attention=any(s.get('account')==self.account and s.get('send_state')=='uncertain'
for s in self._pending.values())
self._save()
self._reply_wakeup.set()
for key,state in removed:
self.log(f"[协议转人工] {state.get('display_name','')} · 自动队列已移交人工;发送结果未作确认,不自动重发")
try:
from queue_log import QueueLog
QueueLog().append(key,state.get('display_name',''),'转人工',
'用户已确认人工接管;保留发送流水和去重记录,不自动重发')
except Exception:pass
self._listening_progress()
return result
''' + old)
compile(text, str(protocol), 'exec')
protocol.write_text(text, encoding='utf-8')
for root in (Path('C:/wechat_rpa'), Path('C:/kefu/wechat_rpa')):
path = root / 'gui_runtime.py'
text = path.read_text(encoding='utf-8')
def replace(old, new, count=1):
global text
assert text.count(old)==count, (str(path), old[:60], text.count(old), count)
text=text.replace(old,new)
replace('def delete_pending_reply_file(keys, path=""):', 'def delete_pending_reply_file(keys, path="", *, handoff=False):')
replace(' removed = []\n for key in sorted(requested):\n state = pending.pop(key, None)\n if isinstance(state, dict):\n removed.append((key, state))', ''' removed = []
protocol = raw.get("transport") == "protocol"
if handoff and protocol and not isinstance(raw.get("seen", {}), dict):
result["error"] = "协议去重记录格式不正确,无法安全转交人工"
return result
for key in sorted(requested):
state = pending.get(key)
if not isinstance(state, dict):
continue
if handoff and str(state.get("send_state") or "") == "sending":
result["protected"].append(key)
continue
if handoff and protocol and state.get("dedup_key"):
raw.setdefault("seen", {})[key] = state["dedup_key"]
pending.pop(key, None)
removed.append((key, state))''')
replace(' if not removed:\n return result\n\n directory =', ' if not removed:\n result["missing"] = sorted(requested.difference(result["protected"]))\n return result\n\n directory =')
replace(' result["missing"] = sorted(requested.difference(result["deleted"]))', ' result["missing"] = sorted(requested.difference(result["deleted"]).difference(result["protected"]))')
replace(' "手动删除",\n "用户从回复队列中手动删除;本条未完成回复不再自动发送",', ''' "转人工" if handoff else "手动删除",
("用户已确认人工接管;发送结果未作确认,保留发送流水和去重记录,不自动重发"
if handoff else "用户从回复队列中手动删除;本条未完成回复不再自动发送"),''')
replace('\n\nclass LogQueue:', '''
def handoff_pending_reply_file(keys, path=""):
"""Retire stopped work after explicit human takeover; preserve send evidence."""
result = delete_pending_reply_file(keys, path, handoff=True)
result["handed_off"] = list(result.get("deleted") or [])
return result
class LogQueue:''')
replace(' self._queued_cancellations = set()', ' self._queued_cancellations = set()\n self._queued_handoffs = set()')
replace(' def retry_pending_tasks(self, keys):', ''' @staticmethod
def _handoff_bot_tasks(bot, keys):
handoff = getattr(bot, "handoff_pending_replies", None)
if callable(handoff):
return handoff(keys)
result = bot.cancel_pending_replies(keys)
result["handed_off"] = list(result.get("deleted") or [])
return result
def handoff_pending_tasks(self, keys):
"""Keep explicit takeover separate from ordinary cancellation at startup."""
requested = {str(key or "").strip() for key in (keys or [])}
requested.discard("")
if not requested:
return {"handed_off": [], "deleted": [], "missing": [], "protected": [], "scheduled": []}
with self._cancel_lock:
bot = self.bot
if bot is None:
self._queued_handoffs.update(requested)
return {"handed_off": [], "deleted": [], "missing": [], "protected": [], "scheduled": sorted(requested)}
return self._handoff_bot_tasks(bot, requested)
def _drain_queued_handoffs(self, bot):
with self._cancel_lock:
requested = set(self._queued_handoffs)
self._queued_handoffs.clear()
if requested:
self._handoff_bot_tasks(bot, requested)
def retry_pending_tasks(self, keys):''')
drains = text.count(' self._drain_queued_cancellations(bot)')
replace(' self._drain_queued_cancellations(bot)', ' self._drain_queued_handoffs(bot)\n self._drain_queued_cancellations(bot)', drains)
compile(text, str(path), 'exec')
backup = root / 'backups/review-visibility-20260916'
backup.mkdir(parents=True, exist_ok=True)
assert not (backup / path.name).exists(), 'Backup already exists; do not rerun'
shutil.copy2(path, backup / path.name)
path.write_text(text, encoding='utf-8')
shutil.copy2(Path(__file__).with_name('test_review_handoff.py'), root / 'test_review_handoff.py')
print('Updated explicit handoff: protocol engine and both runtime modules.')