from pathlib import Path import json,hashlib,shutil,ast folder=Path(__file__).resolve().parent target=Path('C:/wechat_rpa');payload=folder/'payload' record=json.loads((folder/'applied.json').read_text(encoding='utf-8')) backup=Path(record['backup']) original={r['path']:r for r in record['manifest']} def sha(p): return hashlib.sha256(p.read_bytes()).hexdigest() def put(name,text): path=target/name if name not in original: before=sha(path) if path.is_file() else None if before: saved=backup/name;saved.parent.mkdir(parents=True,exist_ok=True);shutil.copyfile(path,saved) original[name]={'before_sha256':before} for root in [target,payload]: dest=root/name;dest.parent.mkdir(parents=True,exist_ok=True) dest.write_text(text,encoding='utf-8',newline='\n') put('INTEGRATION_20260916.md',r'''# 协议版与知识中心整合 2026-09-16 将 C:\kefu 的近期功能整合到 C:\wechat_rpa。保留协议版 v1.4.16 的客户端、视觉/协议模式、原账号发送、回执去重、联系人验证跳过及企业微信版本适配。 ## 已接入 - 智能体角色、规则与全部授权账号视图:原有后端能力保留,补齐完整管理前端。 - 归档性能:分页与游标、租户过滤、账号统计按需读取、导出查询优化。 - 知识加工:范围预览、规则/指定模型整理、任务进度、暂停/继续/取消、失败换模型续跑与增量整理。 - 异常处理:模型字段异常保留脱敏问答为待审草稿,已驳回知识支持删除。 - 全量审核:指定账号或全部授权账号,跨全部页面后台处理;通过后进入待发布,异常保留原因。 - 检索:只引用有效且已发布的知识;账号隔离、来源与版本复核、引用留痕、可读错误提示。 - 协议回复:经过模型网关的回复接入知识检索,保留任务 ID、知识引用和裁判风险;高风险回复进入原有审核流程。 - 客户端「管理知识」打开后台知识中心,数量来自云端已发布知识。知识入口使用正确的 hash 路由。 ## 使用方式 使用当前已配置的后台时,退出并重新启动本目录的源码客户端;在客户端同步后台配置后,进入「AI 人格与能力 → 管理知识」。选择账号,整理聊天、审核、发布,再开启该账号「客服使用知识」。协议模式通过该账号的模型网关使用知识,本机直连模型不经过服务端知识检索。 在管理端「知识与审核」可一键审核此账号或全部授权账号,不需要逐页全选。审核通过仅为待发布,发布后才会参与检索。 本目录也能独立运行完整后台: ```powershell cd C:\wechat_rpa python run_backend.py --db C:\wechat_rpa\backend.db ``` 启动管理 API、模型网关、知识 Worker。管理页面位于 `http://127.0.0.1:8766/#/knowledge/center`,无需为前端另起开发服务。已保留当前配置和数据库;本次验收仅使用临时测试数据库,没有启动此命令处理真实任务。 前端源码与生产资源位于 `admin-web`。再次修改前端时先安装其 pnpm 工作区依赖,再执行 `pnpm --filter @vben/web-antd typecheck`、`pnpm --filter @vben/web-antd build`。已附带本次通过浏览器验收的 dist。 Linux 服务端部署入口为 [deploy/im-admin/README.md](deploy/im-admin/README.md)。知识功能说明见 [KNOWLEDGE.md](KNOWLEDGE.md)。 ## 验证 - 知识、任务、模型输出、全量审核、检索和网关:96 项通过。 - 新增协议与知识联动、独立管理端及桌面状态:7 项通过。 - 管理后台、归档、性能、智能体和路由:326 项通过。 - 协议、联系人、版本适配、打包规则和 Qt:221 项中 217 项通过,4 项因未安装可选 Unicorn CPU 模拟器跳过。 - 客户端登录、归档上传、回复安全、防自回复:496 项通过。 - 合计 1142 项通过、4 项跳过。协议发送使用模拟传输,没有向真实客户发送测试消息。 - 独立管理页:正常检索仅命中已发布知识;空白、单字、超长问题与后端错误显示正确,无页面脚本异常。 - 跨账号页面验收:77 条模拟草稿中 75 条通过进入待发布、2 条保留异常原因,发布数仍为 0;关闭弹窗后任务继续。 ## 交付与回退 本次交付是源码、前端生产资源和部署配置。现有 dist 中已打包的 EXE/安装包未重建,线上服务器未部署;已有运行中的进程需重启才加载新源码。 修改前文件备份:`backups/integration-20260916-151604`。改动清单与 SHA-256 见 `INTEGRATION_MANIFEST_20260916.json`,测试结果见 `INTEGRATION_VERIFICATION_20260916.json`。原数据库、密钥、聊天归档和个人配置没有被本次整合覆盖。 需要回退时先停止使用本目录源码的服务和客户端,再按清单恢复备份中的原文件。不要覆盖运行数据。知识功能使用后不要恢复旧数据库,否则会丢失之后新增的聊天与知识。 ''') put('deploy/im-admin/README.md',r'''# 从协议版目录部署完整管理后台 本部署目录包含管理 API、模型网关和知识 Worker,使用同一数据库卷。构建上下文为 C:\wechat_rpa(Linux 上为其对应源码根目录)。桌面协议发送仍运行在 Windows 客户端,服务端不启动企业微信。 ## 首次部署 1. 将本源码目录及 `admin-web/apps/web-antd/dist` 放到服务器。 2. 将 `.env.example` 复制为本目录 `.env`,填写现有账号服务地址。语义检索为可选项,参照根目录 KNOWLEDGE.md;未配置时使用关键词检索。 3. 在本目录建立 `data`,允许容器 UID 10001 写入。迁移已有库时使用 SQLite backup API 或停止写入后复制数据库及配套密钥;不要复制正在写入的裸数据库文件。 4. 在源码根目录运行: ```bash docker compose -f deploy/im-admin/compose.yaml up -d --build api gateway knowledge-worker docker compose -f deploy/im-admin/compose.yaml ps docker compose -f deploy/im-admin/compose.yaml logs --tail=100 knowledge-worker ``` 管理 API 与页面监听宿主机 `127.0.0.1:18766`,模型网关监听 `127.0.0.1:18770`。对外访问使用反向代理。同目录 Nginx 配置是原部署示例,需按实际域名、日志路径与 TLS 配置调整,不能原样覆盖现有站点。 镜像名称 `zyt/wecom-admin:protocol-integrated`。`data` 不参与镜像构建,`.dockerignore` 仅允许服务端 Python、依赖清单和前端生产资源。镜像构建不会打包本机配置、聊天数据或密钥。 ## 更新与回退 更新前保留当前镜像并备份数据库及密钥。启动时自动补齐知识相关表和索引;知识 Worker 负责分批加工和全量审核任务。关闭页面不会停止已启动的后台任务。 需要停用知识引用,可先在管理端关闭对应账号的「客服使用知识」。回退旧服务前停止知识 Worker,保留新表与运行数据。不要用旧库覆盖更新后产生的聊天归档与知识。 本配置已适配独立协议目录的文件布局;本次没有执行线上部署或 Docker 镜像构建。根目录 `backend_deploy` 和 `BACKEND_DEPLOY.md` 保留为历史资料,当前知识版本使用本目录。 ''') name='KNOWLEDGE.md' text=(target/name).read_text(encoding='utf-8') text=text.replace(r'cd C:\kefu\wechat_rpa',r'cd C:\wechat_rpa') text=text.replace('所有写操作必须选择具体账号。「全部账号」只提供授权范围内的汇总视图。知识不跨租户共享。','整理、编辑、发布与检索测试选择具体账号;全量审核支持全部授权账号并按租户分别处理。「全部账号」也提供授权范围内的汇总视图。知识不跨租户共享。') text=text.replace('当前生产部署使用根目录', '本目录的完整部署入口使用根目录').replace('`wechat_rpa/backend_deploy`','`backend_deploy`') text=text.replace('在生产部署目录执行:','在源码根目录执行(详见 `deploy/im-admin/README.md`):') text=text.replace('docker compose up -d --build api gateway knowledge-worker','docker compose -f deploy/im-admin/compose.yaml up -d --build api gateway knowledge-worker') text=text.replace('docker compose logs --tail=100 knowledge-worker','docker compose -f deploy/im-admin/compose.yaml logs --tail=100 knowledge-worker') put(name,text) name='BACKEND.md';text=(target/name).read_text(encoding='utf-8') text=text.replace('项目内置了一个零额外依赖的配置后台,用于统一管理登录账号、角色、AI 模型和 MCP 配置。','项目内置配置后台,用于统一管理登录账号、角色、AI 模型、MCP 和聊天知识库。') old='> 如果要把后台部署到另一台电脑、局域网服务器或云服务器,请直接查看独立文档 [BACKEND_DEPLOY.md](BACKEND_DEPLOY.md)。部署端只需要 `admin_api.py`(及它依赖的 `admin_backend.py` 数据层),不需要上传桌面端代码或安装桌面端依赖。' new='> 当前协议版已包含完整前端、知识中心和 Worker。使用说明见 [INTEGRATION_20260916.md](INTEGRATION_20260916.md),服务端部署见 [deploy/im-admin/README.md](deploy/im-admin/README.md)。完整后台应使用 `python run_backend.py --db backend.db` 启动 API、网关和知识 Worker;单独启动 API 不执行后台知识任务。' assert old in text;text=text.replace(old,new) text=text.replace('[BACKEND_DEPLOY.md](BACKEND_DEPLOY.md)','[deploy/im-admin/README.md](deploy/im-admin/README.md)') put(name,text) name='AGENT_SYNC_20260916.md';text=(target/name).read_text(encoding='utf-8') text+='\n\n## 后续完整整合\n\n同日后续整合已补齐本文当时尚未同步的知识库、归档性能和管理前端,另补充协议回复知识引用与风险审核。本文保留此前同步记录;当前功能和启动方式以 [INTEGRATION_20260916.md](INTEGRATION_20260916.md) 为准。\n' put(name,text) verification={'date':'2026-09-16','target':str(target),'passed':1142,'skipped':4, 'skip_reason':'Optional Unicorn CPU emulator not installed', 'suites':{'knowledge_and_gateway':{'passed':96},'protocol_knowledge_integration':{'passed':7}, 'backend_archive_agents_router':{'passed':326},'protocol_qt_packaging':{'passed':217,'skipped':4},'client':{'passed':496}}, 'browser_search':json.loads((folder/'qa/result.json').read_text(encoding='utf-8')), 'browser_review':json.loads((folder/'review-qa/result.json').read_text(encoding='utf-8')), 'artifacts':str(folder),'live_customer_messages_sent':False,'live_server_deployed':False,'installer_rebuilt':False} put('INTEGRATION_VERIFICATION_20260916.json',json.dumps(verification,ensure_ascii=False,indent=2)+'\n') # Validate complete staged payload and all protocol programs intentionally retained. manifest=[] for path in sorted(payload.rglob('*')): if not path.is_file():continue relative=path.relative_to(payload).as_posix() assert sha(path)==sha(target/relative),relative if path.suffix=='.py':ast.parse(path.read_text(encoding='utf-8-sig')) manifest.append({'path':relative,'sha256':sha(path),'bytes':path.stat().st_size, 'before_sha256':original[relative]['before_sha256']}) for name,digest in record['preserved'].items():assert sha(target/name)==digest,name record.update(manifest=manifest,files=len(manifest),verified=True,verification='INTEGRATION_VERIFICATION_20260916.json') for path in [folder/'final.json',target/'INTEGRATION_MANIFEST_20260916.json']: path.write_text(json.dumps(record,ensure_ascii=False,indent=2),encoding='utf-8') print(json.dumps({'files':len(manifest),'preserved_programs':len(record['preserved']),'passed':1142,'skipped':4,'backup':str(backup)},ensure_ascii=False))