Files
wx-win-agent/docs/WxAgent-远程控制面架构与部署说明-v1.0.md
T

33 KiB
Raw Blame History

WxAgent 远程控制面架构与部署说明 v1.0

日期:2026-09-12 状态:当前实现与真机验证基线;不是生产安全认证或规模验收报告。 关联协议:WxAgent-远程协议草案-v1.0.md 关联计划:WxAgent-远程多节点控制与白名单数据上报开发计划.md

本文说明当前仓库中 Go 控制面、React 管理端、Windows Desktop Agent、节点诊断 CLI 的实际架构、数据边界、部署步骤、验证方法和已知限制。命令示例中的 Token、密码、账号标识和路径均使用占位符;不要把真实凭据写入仓库、命令历史或日志。

1. 适用范围与结论

当前闭环已经支持:

  • 多节点注册、Bearer Token 认证、心跳和在线状态;
  • 控制面持久化任务队列、任务租约、幂等键、取消请求、结果和审计;
  • send-text 远程文本发送;
  • 远程读取可见会话、联系人/群和当前 UI 可见消息历史;
  • 节点本地白名单过滤、稳定身份校验、结果范围化保存和断线补传;
  • React 管理页面登录、节点/任务/事件/审计查询和 AI 消息处理流程;
  • AI 实时事件/定时读取编排、JSON Schema 输出校验和受控自动回复;
  • Gitea Actions 构建并推送控制面容器镜像;
  • 直接 TLS、节点 mTLS、客户端证书指纹撤销文件和外部 *_FILE Secret 注入;
  • 数据文件 active/passive 单写锁、自动备份、保留期限和 /readyz 就绪检查;
  • 有界的就绪/只读任务并发基线脚本。

当前明确不提供:

  • 任意 Shell、PowerShell、文件系统或代码执行;
  • 控制面直接修改节点白名单;
  • 非白名单会话、完整微信数据库或数据库密钥上传;
  • 远程全量数据库历史读取;消息读取只来自当前微信 UI 可见历史;
  • active-active 多实例、跨节点复制和托管式 Secret/PKI 服务;
  • /api/v1/sessions/open 的远程导航能力。当前该接口可能返回 409 CapabilityDisabled,不影响只读任务接口。

2. 总体架构

┌──────────────────────────┐
│ 浏览器 / 远程 Web 管理端  │
│ 登录、节点、任务、审计     │
└────────────┬─────────────┘
             │ HTTPS(内网验证可显式使用私有 HTTP)
┌────────────▼─────────────┐
│ Go Control Plane          │
│ API / Web / 认证 / 审计   │
│ 任务队列 / 租约 / JSON存储 │
└────────────┬─────────────┘
             │ 节点主动出站 HTTP/HTTPS
             │ 当前实现:心跳 + 最长30秒任务长轮询
┌────────────▼─────────────┐
│ Windows Desktop Agent     │
│ 注册、心跳、轮询、结果上报 │
│ 本地任务账本 / 事件队列    │
└────────────┬─────────────┘
             │ 同一微信窗口进入单一命令队列
┌────────────▼─────────────┐
│ FlaUI.UIA3 / Win32       │
│ 已登录、未锁定的交互桌面   │
└────────────┬─────────────┘
             │
        Weixin.exe

本机诊断入口:WxAgent.Host(只读/诊断)
本机常驻入口:双击 WxAgent.Tray → WxAgent.Service127.0.0.1:5088

2.1 组件与代码边界

组件 目录 技术 职责
Core node-agent/WxAgent.Core .NET 8 协议模型、白名单、账号上下文、任务账本、事件队列和纯逻辑规则
Windows node-agent/WxAgent.Windows .NET 8 Windows FlaUI/UIA、Win32、微信会话/消息读取和写操作
Service node-agent/WxAgent.Service .NET 8 Windows 本机 HTTP API、权限、操作队列、远程 Agent Hosted Service
Host node-agent/WxAgent.Host .NET 8 Windows 只读/诊断 CLI;不提供 serve 或配置修改
Tray node-agent/WxAgent.Tray .NET 8 Windows 交互式用户会话中的常驻托盘、服务生命周期和配置重载
Control Plane control-plane Go 1.23 节点 API、Web API、任务/事件/审计持久化和静态页面服务
Web control-plane/web React/Vite 控制面管理页面,构建后嵌入 Go 二进制

当前控制面是单进程 MVP:Go 二进制内嵌 React 静态资源,数据写入一个 JSON 文件。没有独立数据库、消息队列、节点入站端口或本地 Web/MCP 暴露到远程网络。

2.2 当前传输模型与计划模型的区别

开发计划保留了未来 mTLS WebSocket/等价长连接的架构方向;当前代码实际使用 HTTP/HTTPS 请求

  1. 节点用 Bearer Token 调用注册接口;
  2. 节点周期性发送心跳,默认间隔 10 秒;
  3. 节点按账号轮询任务,服务端支持 wait_seconds=0..30 的长轮询;
  4. 节点确认、开始、续租并回传最终结果;
  5. 节点重启后从本地账本补传已完成但未确认的结果。

因此,部署当前版本不需要给 Windows 节点开放公网入站端口,也不需要部署 RabbitMQ。生产切换到 HTTPS/mTLS 或长连接前,应先完成独立协议、证书生命周期和回归验收。

3. 请求与任务流

3.1 远程写任务

Web 用户登录
  → POST /v1/tasks
  → 控制面原子写入 Pending
  → 节点轮询取得租约 lease_generation
  → 节点写入 remote-task-ledger.json
  → POST ack,中心变为 Accepted
  → 节点写入 Running,再 POST start
  → 复核活动账号、UI 状态和目标会话
  → 执行单窗口 UIA 写操作
  → 节点先保存最终结果,再 POST result
  → Web GET /v1/tasks/{task_id}

任务绑定 node_id + account_id + kind + idempotency_key。同一节点/账号/幂等键使用不同载荷会返回 IdempotencyConflict;重复提交相同载荷返回原任务,不创建第二个任务。

send-text 的节点白名单载荷为:

{
  "target_id": "session_item_文件传输助手",
  "text": "仅用于已批准测试会话的文本",
  "confirmed": true
}

节点不接受 Shell、PowerShell、路径或任意代码。发送消息属于可能产生副作用的写操作,旧执行者是否停止无法证明时进入 ResultUnconfirmed,不得自动重放。

3.2 远程只读任务

远程读取复用同一任务队列,不增加节点入站读取 API:

HTTP 接口 节点任务 kind 用途
POST /v1/reads/sessions read-sessions 读取当前可见会话,结果按白名单过滤
POST /v1/reads/contacts read-contacts 读取联系人或群,结果按联系人稳定 ID 过滤
POST /v1/reads/messages read-messages 读取一个已授权会话的当前 UI 可见历史

公共接口先由控制面校验请求结构并创建任务;节点执行前再次按当前本地配置校验:

  • 全局上报开关必须开启;
  • 目标账号必须存在且启用;
  • 会话必须 enabled=trueidentityVerified=true
  • 数据类型必须被授权;
  • 消息任务的 chat_id 必须精确匹配唯一授权会话;
  • 会话读取结果按 AutomationId 过滤;联系人读取结果按数据库稳定联系人 ID 过滤。

分页参数 limit 为 1200,默认 50offset 不得为负。单次任务请求体上限为 64 KiB,最终结果上限为 512 KiB。消息结果不是完整数据库历史,也不承诺跨页面的事务快照。

3.3 任务状态与租约

Pending → Accepted → Running → Succeeded
                              ├→ Failed
                              └→ ResultUnconfirmed

未开始且可证明无副作用:Pending/Accepted → Cancelled 或 Expired

当前默认参数:

参数 默认值 说明
任务租约 30 秒 控制面 LeaseTTL
心跳间隔 10 秒 节点周期
心跳超时 45 秒 Web 查询节点时标记 Offline
Web 会话 8 小时 控制面内存会话
任务轮询窗口 030 秒 wait_seconds
备份间隔 5 分钟 WXAGENT_CONTROL_PLANE_BACKUP_INTERVAL
事件正文 16 KiB 节点协议上限
任务载荷 64 KiB 节点/中心共同限制
任务结果 512 KiB 含读取内容

取消请求只设置 cancel_requested_at,不把任务直接伪造为已取消。终态不能被旧租约或旧结果覆盖;节点重启发现 Accepted/Running 无最终结果时,生成 ResultUnconfirmed,不重放 UI 操作。

4. 认证、授权与隐私边界

4.1 两类身份

身份 认证方式 可访问接口
Web 用户 POST /v1/auth/login 换取内存 Bearer 会话 Web 节点、任务、读取、事件和审计接口
Windows 节点 节点专用 Authorization: Bearer <node-token> 注册、心跳、任务轮询/租约/结果和事件上报

节点 Token 按 node_id 固定绑定。请求路径、注册体和 Token 中的节点 ID 不一致时拒绝。Token 只从启动环境读取,不写入控制面 JSON 数据文件;密码按当前 MVP 配置从环境读取并在内存中比较。

所有请求生成或传递 X-Correlation-Id;错误只返回固定错误码、通用消息和 Correlation ID。控制面响应设置 Cache-Control: no-storeX-Content-Type-Options: nosniffReferrer-Policy: no-referrer

4.2 Agent 连接是数据授权边界

Agent 安装并成功连接控制面即完成节点及数据同步授权,无需额外用户确认。连接注册会为当前已验证账号授予规范化只读数据范围;远程 Web 不能扩大到原始数据库、密钥或未脱敏内容,也不能改变只读边界。

有效数据授权仍要求:

  1. 节点连接认证成功;
  2. 账号身份已验证且稳定;
  3. 数据来自只读数据库/UI 快照的规范化记录;
  4. 数据类型属于当前同步协议允许范围。

非验证账号、原始微信数据库、数据库密钥、未脱敏 UI 树和普通日志中的完整内容仍不得进入中心 API、中心文件或节点待发送队列。连接授权只覆盖数据同步;验证写操作、消息监听和其他副作用继续由各自运行开关控制。

任务结果只有读取任务可以携带 content。读取结果在节点账本中与连接授权代次一起保存,补传时沿用该代次并再次经过当前节点认证检查;没有连接授权时只保存/回传控制元数据。

4.3 标识与数据范围

  • 会话读取使用微信当前 UI 暴露并确认的稳定 AutomationId,当前示例形如 session_item_*;不使用昵称、PID、窗口句柄或 UIA RuntimeId 作为持久授权身份。
  • 联系人读取使用只读联系人数据库提供的稳定联系人 ID;显示名称只用于展示或辅助定位。
  • 消息读取要求指定一个精确 chat_id,且该 ID 在当前可见会话中唯一。
  • 当前只读消息历史来自微信 UI 可见内容,不是数据库全量扫描,也不通过远程接口暴露数据库密钥或原始数据库。
  • 默认真机验证只使用“文件传输助手”“Hao 豪”“吉祥三宝”“消息测试专用群组”。未得到明确授权不得操作其他真实联系人或群聊。

5. 控制面接口清单

5.1 健康检查与 Web 接口

GET  /healthz
POST /v1/auth/login
GET  /v1/nodes
GET  /v1/nodes/{node_id}
POST /v1/tasks
GET  /v1/tasks?node_id=&account_id=&limit=
GET  /v1/tasks/{task_id}
POST /v1/tasks/{task_id}/cancel
POST /v1/reads/sessions
POST /v1/reads/contacts
POST /v1/reads/messages
GET  /v1/events
GET  /v1/audit
GET  /v1/ai/tools
GET  /v1/ai/flows
POST /v1/ai/flows
GET  /v1/ai/runs
GET  /

/healthz 不要求认证,只返回 status、协议版本和 Correlation ID。其余业务接口要求 Web Bearer 会话。

读取请求的公共字段:

{
  "node_id": "windows-direct-20260911",
  "account_id": "<verified-account-id>",
  "idempotency_key": "read-sessions-20260912-001",
  "limit": 50,
  "offset": 0,
  "not_after": "2026-09-12T09:00:00Z"
}

各接口追加字段:

// /v1/reads/contacts
{"groups_only": false, "contains": "file"}

// /v1/reads/messages
{"chat_id": "session_item_文件传输助手", "include_content": true}

提交成功返回 202 Accepted

{
  "task_id": "<task-id>",
  "status": "Pending",
  "duplicate": false,
  "state_version": 1
}

随后使用 GET /v1/tasks/{task_id} 读取状态和结果。不要用重复提交代替轮询,也不要把 PendingRunning 当作业务成功。

5.2 节点接口

POST /v1/nodes/register
POST /v1/nodes/{node_id}/heartbeat
GET  /v1/nodes/{node_id}/tasks?account_id=&wait_seconds=0..30
POST /v1/nodes/{node_id}/tasks/{task_id}/ack
POST /v1/nodes/{node_id}/tasks/{task_id}/start
POST /v1/nodes/{node_id}/tasks/{task_id}/renew
POST /v1/nodes/{node_id}/tasks/{task_id}/result
POST /v1/nodes/{node_id}/events

节点注册上报协议版本、Agent 版本、能力、白名单计数和账号摘要;心跳上报微信进程/登录/锁屏状态、活动账号、队列长度和配置版本。节点接口不能由 Web Bearer 会话调用。

5.3 常见错误

错误码 处理
Unauthorized 检查 Web 会话或节点 Token
NodeIdentityMismatch Token、路径和请求体中的节点 ID 不一致
NodeNotReady / AccountNotReady 等待节点注册并确认账号
ChatNotAuthorized 本地白名单未允许
ChatIdentityUnconfirmed 稳定身份不唯一或未重新确认
InvalidPagination limit 不在 1200 或 offset 小于 0
IdempotencyConflict 幂等键复用但任务参数不同
LeaseMismatch 租约过期或执行代次不是当前值
ContentNotAllowed 非读取任务试图回传内容
ResultUnconfirmed 无法证明 UI/业务副作用或旧执行者已停止
CapabilityDisabled 当前能力显式未启用,例如远程打开会话

6. 持久化与备份

6.1 控制面

容器内默认路径:

/data/control-plane-data.json
/data/control-plane-data.json.lock
/data/backups/control-plane-data.json.<unix-ns>.json

Store 在进程内 Mutex 和数据文件旁的非阻塞 flock 下执行读写;同一数据卷同时只允许一个活动控制面实例。写入临时文件、Sync 后原子替换,并在替换父目录后同步目录,文件权限为 0600。按 BackupInterval(默认 5 分钟)把旧版本写入备份目录并按 BackupCount 删除旧备份;任务、事件和审计按配置的保留时长在下一次写入前清理,运行中任务不会被清理。持久化对象包括:

PersistedState
├── nodes   节点注册、状态、账号摘要、心跳
├── tasks   任务载荷、租约、状态版本、结果
├── events  已接收白名单事件
└── audit   登录、任务和节点操作审计

当前没有数据库事务或 active-active HAflock 提供的是共享卷上的 active/passive 单写保护。自动备份和按时长保留已实现,但异地复制、恢复演练和备份监控仍是部署门禁。升级/重建容器前应使用 scripts/control-plane-data.sh backup,恢复前停止活动实例并使用 restore,恢复时保持文件权限并确保只有一个控制面实例挂载该数据文件。

6.2 Windows 节点

建议部署目录:

C:\Users\Rogee\wx-agent\
├── WxAgent.Host.exe
├── WxAgent.Tray.exe
├── service.json
├── credentials.json
├── remote.json
├── data\remote-task-ledger.json
├── data\remote-event-queue.json
└── wxagent.log

remote.json 不提供 CLI 修改入口;远程任务账本记录任务指纹、最终结果、ReportingScopes 和是否已上报。节点重启时不重放未完成 UI 任务。Token、凭据文件和含读取结果的账本必须使用 Windows 用户 ACL 保护,不应复制到工单、日志或仓库。

7. Gitea Actions 构建与发布

7.1 前置条件

仓库:https://git.ipao.vip/rogee/wx-win-agent.git 工作流:.gitea/workflows/build-web-image.yml 镜像构建文件:control-plane/Dockerfile

在 Gitea 账号级 Actions Secrets 配置:

REGISTRY_TOKEN=<可推送 git.ipao.vip 容器仓库的 Token>

不要把 Token 放入 workflow、仓库变量、Dockerfile 或镜像层。工作流使用仓库所有者作为 Registry 用户名。

7.2 触发与标签

工作流在以下情况运行:

  • main 分支 push
  • v* Git 标签 push
  • Gitea Actions 手动 workflow_dispatch

镜像名为:

git.ipao.vip/rogee/wx-win-agent

每次构建推送:

sha-<提交 SHA 前12位>

main 额外推送 latestv1.0.0 额外推送 v1.0.0。部署环境应优先固定 SHA 或版本标签,只有开发环境使用 latest

7.3 发布前本地检查

export PATH="$HOME/.dotnet:$PATH"

dotnet restore WxAgent.sln -p:EnableWindowsTargeting=true
dotnet test tests/node-agent/WxAgent.Core.Tests -c Release
dotnet test tests/node-agent/WxAgent.Service.Tests -c Release
dotnet build WxAgent.sln -c Release -p:EnableWindowsTargeting=true

(cd control-plane && go test ./... && go vet ./...)
npm --prefix control-plane/web ci --no-audit --no-fund
npm --prefix control-plane/web run build
git diff --check

远程协议闭环检查:

./scripts/remote-control-smoke.sh

该脚本只使用测试节点/临时控制面,不操作真实微信联系人。

7.4 推送提交

确认工作树中没有凭据、临时配置和真机产物后:

git status --short --untracked-files=all
git diff --check
git add .
git diff --cached --stat
git commit -m "feat: document and publish remote control plane"
git push --set-upstream origin main

推送后在 Gitea Actions 查看构建日志,并核对 SHA 镜像是否存在。不要在未经检查的情况下使用 git add -f 把被忽略的 Token、日志、artifacts 或用户配置加入提交。

8. 控制面部署

8.1 构建本地镜像

docker build \
  --file control-plane/Dockerfile \
  --tag wxagent-control-plane:local \
  control-plane

Dockerfile 分三阶段:Node 构建 React、Go 静态编译、distroless non-root 运行。容器监听 8090,数据卷为 /data,不需要在运行镜像中安装 Node 或 Go。

8.2 单机/内网验证部署

以下示例将控制面绑定到内网主机的 10.1.1.104:18090。真实 Token 和密码通过安全注入替换,不写入 shell 历史:

export NODE_TOKEN='<node-token>'
export WEB_PASSWORD='<web-password>'

mkdir -p /srv/wxagent/control-plane-data

docker rm -f wxagent-control-plane-direct 2>/dev/null || true
docker run -d \
  --name wxagent-control-plane-direct \
  --restart unless-stopped \
  -p 10.1.1.104:18090:8090 \
  -v /srv/wxagent/control-plane-data:/data \
  -e WXAGENT_CONTROL_PLANE_ADDR=0.0.0.0:8090 \
  -e WXAGENT_CONTROL_PLANE_DATA=/data/control-plane-data.json \
  -e WXAGENT_NODE_TOKENS='{"windows-direct-20260911":"'"$NODE_TOKEN"'"}' \
  -e WXAGENT_WEB_USERS='{"admin":"'"$WEB_PASSWORD"'"}' \
  git.ipao.vip/rogee/wx-win-agent:sha-<12位SHA>

curl --fail http://10.1.1.104:18090/healthz

更推荐使用 Compose、systemd 或编排平台的 Secret 注入功能,避免 Token 出现在 docker inspect 的长期配置、进程列表和 shell 历史中。当前镜像支持 *_FILE Secret 文件注入;Secret 文件本身仍由部署平台负责保护和挂载。

单节点兼容环境变量也可用;对应的 *_FILE 形式优先从文件读取:

WXAGENT_NODE_ID
WXAGENT_NODE_TOKEN / WXAGENT_NODE_TOKEN_FILE
WXAGENT_WEB_USER
WXAGENT_WEB_PASSWORD / WXAGENT_WEB_PASSWORD_FILE
WXAGENT_NODE_TOKENS / WXAGENT_NODE_TOKENS_FILE
WXAGENT_WEB_USERS / WXAGENT_WEB_USERS_FILE

多节点/多 Web 用户使用 JSON map

WXAGENT_NODE_TOKENS={"node-a":"token-a","node-b":"token-b"}
WXAGENT_WEB_USERS={"admin":"password-a","auditor":"password-b"}

8.3 生产 HTTPS/mTLS

当前 Go 进程支持直接 TLS。生产不得把明文端口直接暴露到公网;正式部署至少应:

  1. 配置 TLS 证书和私钥,服务端最低 TLS 1.3;
  2. 配置签发节点证书的客户端 CA,并启用 WXAGENT_MTLS_REQUIRE_NODE_CERT=trueWeb 管理用户仍可只使用 HTTPS
  3. 节点使用 PEM 客户端证书和私钥,服务端按 CA 验证;
  4. 将已泄露或失效的客户端证书 DER-SHA256 指纹逐行写入撤销文件。可用 scripts/revoke-client-certificate.sh 原子追加;控制面每次节点认证重新读取该文件,撤销文件缺失/不可读时启动或认证失败。Docker Secret 更新后需重建控制面容器;
  5. Token 和密码通过 WXAGENT_NODE_TOKENS_FILEWXAGENT_WEB_USERS_FILE 等外部 Secret 文件注入,不进入镜像和控制面数据文件;本项目不实现 Token 轮换;
  6. 仅允许管理端、节点网段访问对应路径;
  7. /data 做自动备份、异地复制、恢复演练和保留策略;
  8. 完成失效证书、泄露响应、重放、中心重启、节点断线和并发规模测试。

allowInsecureHttp 只用于显式允许的私有 IP 内网验证;公网 HTTP 永远拒绝。它不是生产加密替代方案。

9. Windows 节点部署

9.1 构建 self-contained 发布包

Linux 构建 Windows 节点:

dotnet restore WxAgent.sln -p:EnableWindowsTargeting=true
dotnet publish node-agent/WxAgent.Host \
  -c Release \
  -r win-x64 \
  --self-contained true \
  -p:EnableWindowsTargeting=true \
  -p:PublishSingleFile=true \
  -p:IncludeNativeLibrariesForSelfExtract=true \
  -p:PublishTrimmed=false

dotnet publish node-agent/WxAgent.Tray \
  -c Release \
  -r win-x64 \
  --self-contained true \
  -p:EnableWindowsTargeting=true \
  -p:PublishSingleFile=true \
  -p:IncludeNativeLibrariesForSelfExtract=true \
  -p:PublishTrimmed=false

上传到已登录 Windows 用户目录(部署时可用 SSH/SCPUI 操作不能在 Session 0 验证):

scp -r node-agent/WxAgent.Host/bin/Release/net8.0-windows10.0.19041.0/win-x64/publish/* \
  rogee@10.1.1.101:'C:/Users/Rogee/wx-agent/'
scp -r node-agent/WxAgent.Tray/bin/Release/net8.0-windows10.0.19041.0/win-x64/publish/* \
  rogee@10.1.1.101:'C:/Users/Rogee/wx-agent/'

升级前先停止托盘进程或交互式启动任务,避免覆盖被占用的单文件发布包;复制完成后校验 SHA-256,再启动 WxAgent.Tray.exe

9.2 本机配置文件

service.json 至少包含本机监听、凭据和数据目录;远程配置建议单独保存:

{
  "listenUrl": "http://127.0.0.1:5088",
  "allowExternal": false,
  "credentialFile": "C:\\Users\\Rogee\\wx-agent\\credentials.json",
  "dataDirectory": "C:\\Users\\Rogee\\wx-agent\\data",
  "remoteConfigurationFile": "C:\\Users\\Rogee\\wx-agent\\remote.json"
}

本机服务默认只监听 127.0.0.1:5088。除非有明确的隔离网络和访问控制,不要启用外部监听;该本机 API 不是控制面节点接入端口。

remote.json 结构如下:

{
  "remote": {
    "authAddress": "https://control.example.com",
    "token": "<node-token>",
    "nodeId": "windows-node-a",
    "activeAccountId": "<verified-account-id>",
    "allowInsecureHttp": false
  },
  "reporting": {
    "enabled": true,
    "configVersion": 1,
    "accounts": [
      {
        "accountId": "<verified-account-id>",
        "enabled": true,
        "allowedChats": [
          {
            "type": "Private",
            "chatId": "<verified-private-id>",
            "enabled": true,
            "identityVerified": true
          }
        ]
      }
    ]
  }
}

当前版本不提供任何命令行配置入口,也不接受通过 CLI 修改远程凭据或上报白名单。Windows Agent 只通过双击 WxAgent.Tray.exe 启动;本机监听、访问凭据、验证写操作、后台消息监听、自动锁屏以及控制面连接(地址、节点 ID、Token、活动账号和 TLS 文件)均在“服务设置...”的“远程连接”页维护。保存远程连接时可将旧 remote.json 配置迁移到托盘管理的 service.json;上报范围由 Agent 连接授权和已验证账号身份统一确定,不再要求单独填写或确认白名单;不得用命令行伪造节点身份或绕过只读边界。

9.3 诊断与只读确认

以下命令仅用于诊断和只读检查,不修改配置、不启动常驻服务:

WxAgent.Host.exe doctor
WxAgent.Host.exe inspect-ui --output artifacts\ui-tree.json
WxAgent.Host.exe session list
WxAgent.Host.exe remote auth show --config C:\Users\Rogee\wx-agent\remote.json
WxAgent.Host.exe remote reporting show --config C:\Users\Rogee\wx-agent\remote.json
WxAgent.Host.exe remote probe run --config C:\Users\Rogee\wx-agent\remote.json

9.4 交互式启动要求

UIA Agent 必须运行在微信所在的已登录、未锁定 Windows 用户会话中。只允许由该用户双击 WxAgent.Tray.exe 启动,不使用 Windows Service、Session 0、serve --config 或任何启动参数。自动锁屏策略在“服务设置...”窗口中配置:

产品验收入口是资源管理器/开始菜单中的 WxAgent.Tray.exe 双击,不是命令行。

部署完成后至少执行:

WxAgent.Host.exe doctor
WxAgent.Host.exe inspect-ui --output artifacts\ui-tree.json
WxAgent.Host.exe smoke

smoke 会向默认测试会话发送唯一标记,是写操作;生产或真实联系人验证前必须明确确认目标。

10. 远程验证操作手册

10.1 控制面登录

curl --fail http://10.1.1.104:18090/healthz
curl --fail http://10.1.1.104:18090/readyz

curl --fail -c /tmp/wxagent.cookies \
  -H 'Content-Type: application/json' \
  -d '{"username":"admin","password":"<web-password>"}' \
  http://10.1.1.104:18090/v1/auth/login

curl --fail -b /tmp/wxagent.cookies \
  http://10.1.1.104:18090/v1/nodes

实际客户端也可把登录响应中的 access_token 作为 Authorization: Bearer 使用。不要把响应 Token 写进共享日志。

10.2 读取会话、联系人和消息

以下示例使用 Web Bearer Token 占位符;每个读取调用应使用新的、可追踪的幂等键:

BASE=http://10.1.1.104:18090
AUTH="Authorization: Bearer <web-access-token>"

curl --fail -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"node_id":"windows-direct-20260911","account_id":"<account-id>","idempotency_key":"sessions-001","limit":50,"offset":0}' \
  "$BASE/v1/reads/sessions"

curl --fail -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"node_id":"windows-direct-20260911","account_id":"<account-id>","idempotency_key":"private-001","groups_only":false,"limit":50,"offset":0}' \
  "$BASE/v1/reads/contacts"

curl --fail -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{"node_id":"windows-direct-20260911","account_id":"<account-id>","idempotency_key":"messages-001","chat_id":"<authorized-session-id>","include_content":true,"limit":20,"offset":0}' \
  "$BASE/v1/reads/messages"

从响应取 task_id 后:

curl --fail -H "$AUTH" "$BASE/v1/tasks/<task-id>"

只有 status=Succeededresult.content 存在时才读取内容;FailedCancelledExpiredResultUnconfirmed 都不能视为读取成功。

10.3 发送测试文本

只对已批准测试目标执行:

curl --fail -H "$AUTH" -H 'Content-Type: application/json' \
  -d '{
    "node_id":"windows-direct-20260911",
    "account_id":"<account-id>",
    "kind":"send-text",
    "idempotency_key":"send-20260912-001",
    "payload":{
      "target_id":"session_item_文件传输助手",
      "text":"wxagent-controlled-test-<unique-marker>",
      "confirmed":true
    }
  }' \
  "$BASE/v1/tasks"

发送任务必须结合任务终态和微信本地历史读回确认;Succeeded 代表 Agent 已确认完成调用,不代替业务侧读回核验。

10.4 并发基线

在 staging 控制面和已注册测试节点上执行;默认只创建 read-sessions 读取任务,不执行写任务:

WXAGENT_SCALE_WEB_PASSWORD='<test-password>' \
  scripts/control-plane-scale-smoke.py \
  --base-url http://127.0.0.1:8090 \
  --node-id node-a --account-id account-a \
  --requests 100 --workers 10

只测就绪接口时不需要 Web 密码或节点参数:

scripts/control-plane-scale-smoke.py --mode ready \
  --base-url https://control.example.com --requests 500 --workers 50

脚本输出成功数、HTTP 状态分布、p50/p95;每次压测应保存版本、配置、机器规格、数据文件大小和结果,不把结果直接当作 1000 路生产验收。

11. 故障排查

现象 优先检查
/healthz 失败 容器、端口映射、绑定地址、防火墙和 /data 权限
Web 登录 401 WXAGENT_WEB_USERS 是否为合法 JSON map,密码是否与当前容器一致
节点注册 401 Token 是否与 node_id 一一绑定,节点是否读到最新 remote.json
节点 Offline Windows 托盘进程、网络、心跳时间、控制面日志和节点 remote status show
SessionLocked 必须恢复微信所在的已登录、未锁定交互会话;Session 0 不算通过
WechatNotLoggedIn 在微信桌面正常完成登录/手机确认,不绕过登录流程
AccountContextUnconfirmed 重新确认活动账号和 UI 绑定,不能只看昵称
读取返回 ChatNotAuthorized 检查全局/账号/会话开关、类型、稳定 ID 和 identityVerified
读取返回空列表 先用本机 session list/联系人读取核对 ID 编码和当前可见范围
读取返回 ChatIdentityUnconfirmed 当前 UI 中不存在唯一匹配的稳定会话,禁止用昵称替代
LeaseMismatch 任务已过租约或有旧执行者;不要重复创建写任务掩盖问题
ResultUnconfirmed 人工核对微信是否已产生副作用;不得自动重放写操作
修改 Token 后仍旧连接 节点远程地址/Token改变需要重启 Agent;控制面凭据改变需要重启容器
/sessions/open 返回 409 当前导航能力未启用;使用本地 UI 或现有只读读取任务,不把它当成传输故障

普通日志只记录错误码、类型、路径、耗时和 Correlation ID,不应通过日志回显消息正文、联系人名称、附件或凭据。排查时使用 Correlation ID 关联控制面审计和节点日志。

12. 当前验证证据与发布门禁

已完成的当前基线验证:

  • 控制面 Docker 镜像构建成功,/healthz 返回 ok/v1
  • Go go test ./...go vet ./... 通过;
  • Core 测试 168/168 通过;Service 测试 20/20 通过;完整 solution Release build 通过;
  • scripts/remote-control-smoke.sh 通过,覆盖注册、心跳、任务租约、幂等、结果回传、读取结果保留和拒绝路径;
  • Windows 节点 windows-direct-20260911 在已登录、未锁定会话中上线,注册 read-sessionsread-contactsread-messages 能力;
  • 真机 send-text 任务 Pending → Running → Succeeded,并用文件传输助手本地历史读回唯一标记;
  • 真机远程读取:会话 1 条、私聊联系人 1 条(filehelper)、群 1 条(53271859539@chatroom)、文件传输助手消息 3 条,四个读取任务均为 Succeeded

提交或部署新版本前必须重新执行相关测试。以下条件满足前不能标记生产完成:

  • 在实际部署环境启用控制面 HTTPS、节点 HTTPS/mTLS 和 Secret 文件挂载;
  • 客户端证书撤销、泄露响应和重新签发演练(不包含 Token 轮换);
  • 自动备份/保留、异地复制、恢复演练、active/passive 故障切换、并发写入和容量上限验证;
  • 节点/控制面重启、断网、租约过期和 ResultUnconfirmed 人工核对;
  • 真实目标规模、长时间运行、数据保留和日志泄漏扫描;
  • 当前微信版本变化后的 doctor、脱敏 UI 树和 smoke 回归。

13. 相关文件

  • control-plane/Dockerfile:控制面多阶段镜像构建;
  • control-plane/server.goprotocol.gostore.gotls.go:HTTP/TLS 路由、协议、持久化和证书撤销;
  • control-plane/compose.production.ymlDocker Secret、TLS/mTLS 和保留配置的生产示例;
  • control-plane/web/React 管理端;
  • .gitea/workflows/build-web-image.ymlGitea 镜像发布;
  • node-agent/WxAgent.Service/RemoteAgentHostedService.cs:节点注册、轮询、任务执行和补传;
  • node-agent/WxAgent.Core/ReportingAuthorization.cs:统一本地白名单授权;
  • node-agent/WxAgent.Core/RemoteTaskLedger.cs:本地任务结果账本;
  • node-agent/WxAgent.Host/RemoteCliCommands.cs:远程认证、状态和白名单 CLI
  • docs/validation/Agent-Install-2026-09-11.mdWindows 安装与真实验证记录;
  • scripts/remote-control-smoke.shLinux/临时控制面闭环检查;
  • scripts/control-plane-scale-smoke.py:就绪/只读任务并发基线;
  • scripts/control-plane-data.sh:控制面数据备份和锁保护的原子恢复;
  • scripts/revoke-client-certificate.sh:计算客户端证书指纹并原子追加撤销列表。