From 7bf994de883a44a54a2b0bc42ec101b00a0814d7 Mon Sep 17 00:00:00 2001 From: Rogee Date: Sun, 20 Sep 2026 17:25:34 +0800 Subject: [PATCH] feat: make Windows Agent configuration GUI-only --- README.md | 4 +- control-plane/README.md | 20 ++--- docs/WebUI-MCP-使用说明.md | 8 +- docs/WebUI-MCP-功能矩阵.md | 2 +- docs/WebUI-MCP-开发计划.md | 1 + ...-远程多节点控制与白名单数据上报开发计划.md | 2 + docs/WxAgent-远程控制面架构与部署说明-v1.0.md | 76 ++++--------------- installer/WxAgent.iss | 10 +-- node-agent/WxAgent.Host/Program.cs | 30 ++------ node-agent/WxAgent.Host/RemoteCliCommands.cs | 9 +++ node-agent/WxAgent.Service/ServiceOptions.cs | 1 + node-agent/WxAgent.Tray/Program.cs | 56 ++++++++------ 12 files changed, 82 insertions(+), 137 deletions(-) diff --git a/README.md b/README.md index ebfa2a8..e520e8d 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,9 @@ dotnet publish node-agent/WxAgent.Host -c Release -r win-x64 --self-contained tr -p:IncludeNativeLibrariesForSelfExtract=true -p:PublishTrimmed=false ``` -## CLI +## CLI(仅诊断/开发验证) + +Windows Agent 不接受命令行启动参数或配置修改;终端用户应双击 `WxAgent.Tray.exe`,在“服务设置...”窗口配置。以下 Host 命令仅用于诊断、只读查询和受控开发验证。 ```powershell WxAgent.Host doctor diff --git a/control-plane/README.md b/control-plane/README.md index 5f08b64..2491238 100644 --- a/control-plane/README.md +++ b/control-plane/README.md @@ -27,7 +27,7 @@ export WXAGENT_NODE_TOKENS='{"node-a":"token-a","node-b":"token-b"}' export WXAGENT_WEB_USERS='{"admin":"change-me","auditor":"read-only-password"}' ``` -可选:`WXAGENT_CONTROL_PLANE_ADDR`(默认 `127.0.0.1:8090`)、`WXAGENT_CONTROL_PLANE_DATA`(默认 `control-plane-data.json`)。凭据可通过环境变量或 `*_FILE` Secret 文件读取,不写入控制面数据文件和日志。节点 `service.json` 可设置 `remoteConfigurationFile` 指向 CLI 管理的 `remote.json`;修改上报配置会在下一轮生效,修改远程地址/Token 后需重启 Agent。 +可选:`WXAGENT_CONTROL_PLANE_ADDR`(默认 `127.0.0.1:8090`)、`WXAGENT_CONTROL_PLANE_DATA`(默认 `control-plane-data.json`)。凭据可通过环境变量或 `*_FILE` Secret 文件读取,不写入控制面数据文件和日志。节点的 `remoteConfigurationFile` 仅用于读取预置配置;Windows Agent 不提供命令行修改配置,启动和本机配置必须通过双击 `WxAgent.Tray.exe` 后的“服务设置...”窗口完成。 生产控制面可直接启用 TLS 和节点 mTLS: @@ -116,20 +116,10 @@ WXAGENT_SCALE_WEB_PASSWORD='' \ ## Windows 手工验收 -1. 在已登录且未锁定的微信桌面会话中运行 `WxAgent.Host doctor` 和 `WxAgent.Host inspect-ui --output artifacts/ui-tree.json`,确认账号绑定使用稳定 `accountId`,会话使用稳定 `AutomationId`,不使用昵称/PID/窗口句柄猜测。 -2. 为节点配置 `remote.json`,例如先执行: - - ```powershell - WxAgent.Host remote auth set --config remote.json --address https://control.example --token $env:WXAGENT_NODE_TOKEN --node node-a --active-account <已确认的accountId> - WxAgent.Host remote reporting enable --config remote.json - WxAgent.Host remote reporting account-add --config remote.json --account <已确认的accountId> - WxAgent.Host remote reporting account-enable --config remote.json --account <已确认的accountId> - WxAgent.Host remote reporting allow --config remote.json --account <已确认的accountId> --type group --chat-id <已确认的稳定chatId> --identity-verified - WxAgent.Host remote probe run --config remote.json - ``` - -3. 在节点 `service.json` 设置 `"remoteConfigurationFile": ""` 后启动 `serve`。控制面应显示节点 `Online`、已验证账号和心跳版本。 -4. 仅使用文件传输助手、`Hao 豪`、`吉祥三宝`、`消息测试专用群组`做发送/接收验证:先验证允许的群聊事件能到达控制面,再执行 `reporting deny`,确认后续正文不再上传;创建 `send-text` 前确认目标账号、稳定 chat ID 和文本。 +1. 在已登录且未锁定的微信桌面会话中双击 `WxAgent.Tray.exe`;首次运行或托盘菜单“服务设置...”打开后,配置本机监听、凭据、验证模式和自动锁屏。 +2. 不使用 `WxAgent.Host serve`、`--config`、`remote auth set`、`remote reporting enable/allow` 或其他命令行配置。`WxAgent.Host doctor`、`inspect-ui`、`remote ... show/probe` 仅用于只读诊断。 +3. 远程配置若已由部署系统预置,托盘服务读取 `remoteConfigurationFile`;当前不通过命令行修改远程地址、Token、节点 ID 或 reporting 白名单。 +4. 仅使用文件传输助手、`Hao 豪`、`吉祥三宝`、`消息测试专用群组`做发送/接收验证:先确认目标账号、稳定 chat ID 和文本;Web/HTTP/MCP 写操作仍必须通过现有确认门禁。 5. 在控制面取消任务并验证节点不执行;断开网络后恢复,确认未完成副作用任务显示 `ResultUnconfirmed`,不会自动重放。 未解锁的 Session 0、昵称、PID、窗口句柄和未确认的运行时身份都应视为验收失败;手工验收不得改写微信数据库或绕过安全机制。 diff --git a/docs/WebUI-MCP-使用说明.md b/docs/WebUI-MCP-使用说明.md index 167a081..6449c38 100644 --- a/docs/WebUI-MCP-使用说明.md +++ b/docs/WebUI-MCP-使用说明.md @@ -6,11 +6,7 @@ 1. 在已登录、未锁定的 Windows 用户会话中运行发布包中的 `WxAgent-Setup.exe`。安装器会创建桌面/开始菜单快捷方式和可选的登录启动项,不需要 PowerShell 或管理员权限。 2. 启动托盘程序后,首次运行会生成唯一的远程访问 Token,并自动打开“服务设置...”窗口。Token 在该窗口中明文展示,可复制或重新生成;不需要单独的凭据窗口,也不支持多 Token。 -3. 如需无托盘运行,仍可复制 `docs/webui-mcp-config.example.json` 后使用: - -```powershell -WxAgent.Host.exe serve --config C:\Users\USERNAME\wx-agent\service.json -``` +3. Windows Agent 不支持无托盘运行、`serve --config` 或其他命令行配置。请双击 `WxAgent.Tray.exe`,在托盘菜单的“服务设置...”窗口中配置监听地址、凭据、验证模式和自动锁屏选项。 `credentials.json` 只保存 SHA-256 大写十六进制摘要,例如: @@ -20,7 +16,7 @@ WxAgent.Host.exe serve --config C:\Users\USERNAME\wx-agent\service.json `AccountIds` 仅为旧凭据格式保留,不再作为账号级数据授权边界;`[]` 可以保留。凭据文件应使用当前用户 ACL,禁止提交仓库。调用方仍必须显式携带 account fingerprint 选择数据和目标,Agent 会验证绑定、窗口和目标一致性。托盘模式的 `service.json` 会保存 `AccessToken` 明文,以便服务设置窗口随时展示;该文件同样只应保存在本机。 -服务运行后通过托盘菜单中的“服务设置...”配置监听地址、端口、远程访问和唯一 Token,不需要手工编辑 `service.json`/`credentials.json`。队列容量、监听会话和监听事件开关属于内部实现/验收策略,不作为用户配置项。若需要保持微信 UI 会话不自动锁屏,在安装器的可选项中启用“防止自动锁屏”;该设置不阻止用户手动锁定。默认只监听 `127.0.0.1:5088`,本机访问控制台不需要 Token,会自动进入。外部监听必须启用远程访问并自行配置防火墙,服务不会自动开放端口;外部监听使用具体 IP,不能使用 `0.0.0.0`/`::`。服务校验可信 Host/Origin;远程浏览器写请求需要 Cookie、CSRF 和可信 Origin,本机浏览器写请求还需要 `X-WxAgent-Local: 1`。HTTP 不加密 Token、Cookie、消息或附件,不直接暴露公网。 +服务运行后通过托盘菜单或双击托盘图标打开“服务设置...”配置监听地址、端口、远程访问、唯一 Token、验证模式和自动锁屏选项,不需要手工编辑 `service.json`/`credentials.json`。不提供命令行配置入口。队列容量、监听会话和监听事件开关属于内部实现/验收策略,不作为用户配置项。自动锁屏设置由服务设置窗口保存;该设置不阻止用户手动锁定。默认只监听 `127.0.0.1:5088`,本机访问控制台不需要 Token,会自动进入。外部监听必须启用远程访问并自行配置防火墙,服务不会自动开放端口;外部监听使用具体 IP,不能使用 `0.0.0.0`/`::`。服务校验可信 Host/Origin;远程浏览器写请求需要 Cookie、CSRF 和可信 Origin,本机浏览器写请求还需要 `X-WxAgent-Local: 1`。HTTP 不加密 Token、Cookie、消息或附件,不直接暴露公网。 远程浏览器访问 `/` 后输入 Token 登录。HTTP/MCP 客户端使用: diff --git a/docs/WebUI-MCP-功能矩阵.md b/docs/WebUI-MCP-功能矩阵.md index 4c227cd..417ad5b 100644 --- a/docs/WebUI-MCP-功能矩阵.md +++ b/docs/WebUI-MCP-功能矩阵.md @@ -49,7 +49,7 @@ ## 运行边界 -- 可通过 `WxAgent.Host serve --config ` 启动;安装版使用托盘程序,服务设置和唯一 Token 均在同一个系统设置窗口完成。默认监听 `127.0.0.1`,外部 IP 必须显式 `allowExternal: true`。 +- Windows Agent 只接受双击 `WxAgent.Tray.exe` 启动;首次运行或托盘菜单/托盘图标双击会打开“服务设置...”配置窗口。`WxAgent.Host serve --config` 及命令行配置入口均禁用。默认监听 `127.0.0.1`,外部 IP 必须显式 `allowExternal: true`。 - 本机回环访问 Web UI、HTTP API 和 Streamable HTTP MCP (`/mcp`) 不需要 Token;远程访问仍使用 Bearer Token,Token 只放 `Authorization`,不放 URL。 - 远程浏览器登录后仅保留短期 HttpOnly SameSite Cookie,写请求需可信 Host/Origin、CSRF;本机浏览器写请求还需同源本机请求头;普通日志不记录正文、Token、Cookie、密钥或完整 UI 树。 - 明文 HTTP 不提供传输保密性,只适合可信隔离网络;不应直接暴露公网。外部监听必须使用具体 IP,不能使用 `0.0.0.0`/`::` 作为工作台地址。 diff --git a/docs/WebUI-MCP-开发计划.md b/docs/WebUI-MCP-开发计划.md index ed6818d..1770189 100644 --- a/docs/WebUI-MCP-开发计划.md +++ b/docs/WebUI-MCP-开发计划.md @@ -4,6 +4,7 @@ > > 状态:规划,尚未实施或验收。用户已确认目标为“浏览器 Web UI + MCP 服务”。 > 本次交付仅为开发计划,不启动服务、不执行微信写操作。 +> 当前运行约束:Windows Agent 仅由双击 `WxAgent.Tray.exe` 启动;本机配置使用“服务设置...”窗口,命令行 `serve` 和任何配置修改入口均不再提供。下文早期 `serve` 仅保留为历史规划,不是当前运行命令。 > 范围变化:原第一阶段排除正式 UI;本文规划其后的控制面阶段。用户已确认 HTTP/Web UI 与 MCP 支持外部访问且共用 Token,不要求 HTTPS,不实现服务端人工审批;不改变 Windows Service、协议及数据库安全边界。 ## 1. 目标与基线 diff --git a/docs/WxAgent-远程多节点控制与白名单数据上报开发计划.md b/docs/WxAgent-远程多节点控制与白名单数据上报开发计划.md index 67814a8..a231830 100644 --- a/docs/WxAgent-远程多节点控制与白名单数据上报开发计划.md +++ b/docs/WxAgent-远程多节点控制与白名单数据上报开发计划.md @@ -3,6 +3,8 @@ > 版本:0.5 > 日期:2026-09-11 > 状态:后续阶段计划 +> +> 当前入口约束:Windows Agent 只允许双击 `WxAgent.Tray.exe`,本机配置通过“服务设置...”窗口完成;`WxAgent.Host` 仅保留只读/诊断 CLI,任何命令行配置修改均禁用。本文中“本地 CLI 配置”属于历史计划措辞,不是当前可执行入口。 ## 1. 目标 diff --git a/docs/WxAgent-远程控制面架构与部署说明-v1.0.md b/docs/WxAgent-远程控制面架构与部署说明-v1.0.md index f08ed07..3013a63 100644 --- a/docs/WxAgent-远程控制面架构与部署说明-v1.0.md +++ b/docs/WxAgent-远程控制面架构与部署说明-v1.0.md @@ -5,7 +5,7 @@ > 关联协议:[`WxAgent-远程协议草案-v1.0.md`](./WxAgent-远程协议草案-v1.0.md) > 关联计划:[`WxAgent-远程多节点控制与白名单数据上报开发计划.md`](./WxAgent-远程多节点控制与白名单数据上报开发计划.md) -本文说明当前仓库中 **Go 控制面、React 管理端、Windows Desktop Agent、节点本地 CLI** 的实际架构、数据边界、部署步骤、验证方法和已知限制。命令示例中的 Token、密码、账号标识和路径均使用占位符;不要把真实凭据写入仓库、命令历史或日志。 +本文说明当前仓库中 **Go 控制面、React 管理端、Windows Desktop Agent、节点诊断 CLI** 的实际架构、数据边界、部署步骤、验证方法和已知限制。命令示例中的 Token、密码、账号标识和路径均使用占位符;不要把真实凭据写入仓库、命令历史或日志。 ## 1. 适用范围与结论 @@ -60,8 +60,8 @@ │ Weixin.exe -本机人工入口:WxAgent.Host CLI -本机常驻入口:WxAgent.Tray → WxAgent.Service(127.0.0.1:5088) +本机诊断入口:WxAgent.Host(只读/诊断) +本机常驻入口:双击 WxAgent.Tray → WxAgent.Service(127.0.0.1:5088) ``` ### 2.1 组件与代码边界 @@ -71,7 +71,7 @@ | 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` 模式和诊断入口 | +| 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 二进制 | @@ -340,7 +340,7 @@ C:\Users\Rogee\wx-agent\ └── wxagent.log ``` -`remote.json` 由 CLI 原子保存;远程任务账本记录任务指纹、最终结果、ReportingScopes 和是否已上报。节点重启时不重放未完成 UI 任务。Token、凭据文件和含读取结果的账本必须使用 Windows 用户 ACL 保护,不应复制到工单、日志或仓库。 +`remote.json` 不提供 CLI 修改入口;远程任务账本记录任务指纹、最终结果、ReportingScopes 和是否已上报。节点重启时不重放未完成 UI 任务。Token、凭据文件和含读取结果的账本必须使用 Windows 用户 ACL 保护,不应复制到工单、日志或仓库。 ## 7. Gitea Actions 构建与发布 @@ -578,76 +578,26 @@ scp -r node-agent/WxAgent.Tray/bin/Release/net8.0-windows10.0.19041.0/win-x64/pu } ``` -可以用 CLI 原子维护,不要手工编辑已在运行中的 Token: +当前版本不提供任何命令行配置入口,也不接受通过 CLI 修改 `remote.json`、Token、节点 ID 或上报白名单。Windows Agent 只通过双击 `WxAgent.Tray.exe` 启动;本机监听、访问凭据、验证写操作和自动锁屏在“服务设置...”配置窗口中维护。远程上报配置尚未提供用户配置容器时保持已有部署配置或禁用,不得用命令行绕过该边界。 -```powershell -$cfg = 'C:\Users\Rogee\wx-agent\remote.json' -$env:WXAGENT_NODE_TOKEN = '' +### 9.3 诊断与只读确认 -WxAgent.Host.exe remote auth set ` - --config $cfg ` - --address https://control.example.com ` - --token $env:WXAGENT_NODE_TOKEN ` - --node windows-node-a ` - --active-account - -WxAgent.Host.exe remote reporting enable --config $cfg -WxAgent.Host.exe remote reporting account-add --config $cfg --account -WxAgent.Host.exe remote reporting account-enable --config $cfg --account -``` - -内网 HTTP 验证必须显式 opt-in,并且地址必须是私有 IP: - -```powershell -WxAgent.Host.exe remote auth set ` - --config $cfg ` - --address http://10.1.1.104:18090 ` - --token $env:WXAGENT_NODE_TOKEN ` - --node windows-direct-20260911 ` - --active-account ` - --allow-insecure-http -``` - -### 9.3 确认并允许会话 - -在微信已登录、桌面未锁定的交互式会话中获取并确认稳定 ID。名称仅用于人工确认,不作为授权键: +以下命令仅用于诊断和只读检查,不修改配置、不启动常驻服务: ```powershell WxAgent.Host.exe doctor WxAgent.Host.exe inspect-ui --output artifacts\ui-tree.json WxAgent.Host.exe session list -WxAgent.Host.exe remote reporting show --config $cfg +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 ``` -确认后只把批准的测试会话加入白名单: - -```powershell -WxAgent.Host.exe remote reporting allow ` - --config $cfg ` - --account ` - --type private ` - --chat-id ` - --identity-verified - -WxAgent.Host.exe remote reporting allow ` - --config $cfg ` - --account ` - --type group ` - --chat-id ` - --identity-verified - -WxAgent.Host.exe remote probe run --config $cfg -``` - -修改远程地址、Token 或节点 ID 后必须重启 Agent;修改 reporting 配置会在下一轮远程循环重新读取。可以从托盘菜单选择“重新加载配置”,或重启 `WxAgent.Tray.exe`。 - ### 9.4 交互式启动要求 -UIA Agent 必须运行在微信所在的已登录、未锁定 Windows 用户会话中。建议使用该用户的登录启动任务运行 `WxAgent.Tray.exe --config ...`,而不是 Windows Service 或 Session 0。`--prevent-auto-lock` 会修改 Windows 电源/锁屏策略,只有明确批准时使用: +UIA Agent 必须运行在微信所在的已登录、未锁定 Windows 用户会话中。只允许由该用户双击 `WxAgent.Tray.exe` 启动,不使用 Windows Service、Session 0、`serve --config` 或任何启动参数。自动锁屏策略在“服务设置...”窗口中配置: -```powershell -WxAgent.Tray.exe --config C:\Users\Rogee\wx-agent\service.json -``` +产品验收入口是资源管理器/开始菜单中的 `WxAgent.Tray.exe` 双击,不是命令行。 部署完成后至少执行: diff --git a/installer/WxAgent.iss b/installer/WxAgent.iss index 8e42157..2129d6b 100644 --- a/installer/WxAgent.iss +++ b/installer/WxAgent.iss @@ -31,19 +31,17 @@ Source: "{#PublishRoot}\wxagent.ico"; DestDir: "{app}"; Flags: ignoreversion [Tasks] Name: "autostart"; Description: "登录 Windows 后自动启动 WxAgent"; GroupDescription: "启动选项:"; Flags: checkedonce -Name: "preventautolock"; Description: "防止自动息屏、睡眠、休眠和锁屏"; GroupDescription: "会话选项:"; Flags: unchecked [Icons] -Name: "{autoprograms}\WxAgent"; Filename: "{app}\WxAgent.Tray.exe"; Parameters: "--config ""{app}\service.json"""; WorkingDir: "{app}" -Name: "{userdesktop}\WxAgent"; Filename: "{app}\WxAgent.Tray.exe"; Parameters: "--config ""{app}\service.json"""; WorkingDir: "{app}" -Name: "{userstartup}\WxAgent"; Filename: "{app}\WxAgent.Tray.exe"; Parameters: "--config ""{app}\service.json"""; WorkingDir: "{app}"; Tasks: autostart +Name: "{autoprograms}\WxAgent"; Filename: "{app}\WxAgent.Tray.exe"; WorkingDir: "{app}" +Name: "{userdesktop}\WxAgent"; Filename: "{app}\WxAgent.Tray.exe"; WorkingDir: "{app}" +Name: "{userstartup}\WxAgent"; Filename: "{app}\WxAgent.Tray.exe"; WorkingDir: "{app}"; Tasks: autostart [Run] Filename: "{sys}\schtasks.exe"; Parameters: "/delete /tn ""WxAgent"" /f"; Flags: runhidden waituntilterminated Filename: "{sys}\taskkill.exe"; Parameters: "/IM WxAgent.Host.exe /F"; Flags: runhidden waituntilterminated Filename: "{sys}\taskkill.exe"; Parameters: "/IM WxAgent.Tray.exe /F"; Flags: runhidden waituntilterminated -Filename: "{app}\WxAgent.Tray.exe"; Parameters: "--prevent-auto-lock --exit"; WorkingDir: "{app}"; Description: "配置会话防止自动锁屏"; Flags: runhidden waituntilterminated; Tasks: preventautolock -Filename: "{app}\WxAgent.Tray.exe"; Parameters: "--config ""{app}\service.json"""; WorkingDir: "{app}"; Description: "启动 WxAgent 托盘程序"; Flags: nowait postinstall skipifsilent +Filename: "{app}\WxAgent.Tray.exe"; WorkingDir: "{app}"; Description: "启动 WxAgent 托盘程序;双击后在服务设置中配置"; Flags: nowait postinstall skipifsilent [UninstallRun] Filename: "{sys}\schtasks.exe"; Parameters: "/delete /tn ""WxAgent"" /f"; Flags: runhidden waituntilterminated; RunOnceId: "DeleteWxAgentTask" diff --git a/node-agent/WxAgent.Host/Program.cs b/node-agent/WxAgent.Host/Program.cs index d145845..dce1816 100644 --- a/node-agent/WxAgent.Host/Program.cs +++ b/node-agent/WxAgent.Host/Program.cs @@ -25,20 +25,8 @@ try } if (args[0] == "serve") - { - ValidateOptions(args, 1, ["--config"], []); - var configPath = Path.GetFullPath(GetRequiredOption(args, "--config")); - var serviceOptions = JsonSerializer.Deserialize( - await File.ReadAllTextAsync(configPath, shutdown.Token), ServiceHost.ConfigurationJson) - ?? throw new ArgumentException("A service configuration is required."); - await using var app = ServiceHost.Build(serviceOptions, new WindowsAgentBackend(new AccountBindingStore(serviceOptions), serviceOptions), - logPath: Path.Combine(Path.GetDirectoryName(configPath)!, "wxagent.log")); - await app.StartAsync(shutdown.Token); - try { await Task.Delay(Timeout.InfiniteTimeSpan, shutdown.Token); } - catch (OperationCanceledException) when (shutdown.IsCancellationRequested) { } - await app.StopAsync(CancellationToken.None); - return 0; - } + throw new WxAgentException(WxAgentErrorCode.InvalidArgument, + "Windows Agent must be started by double-clicking WxAgent.Tray.exe; configuration is GUI-only."); var defaultTimeoutSeconds = ValidateCommandLine(args); using var timeout = CancellationTokenSource.CreateLinkedTokenSource(shutdown.Token); @@ -1030,14 +1018,12 @@ static object ToMessageOutput(ChatMessageSnapshot message, bool includeContent) static int CountNodes(UiNodeSnapshot node) => 1 + node.Children.Sum(CountNodes); static void PrintHelp() => Console.WriteLine(""" -WxAgent.Host commands: - serve --config - remote auth show|set|clear --config - auth set options: --address --token | --token-file --node - [--server-ca-file ] [--client-certificate-file --client-certificate-key-file ] - remote status show --config [--data-dir ] - remote probe run --config [--timeout 60] - remote reporting show|enable|disable|account-add|account-enable|account-disable|allow|deny --config +WxAgent.Host commands (diagnostics/read-only only; configuration is GUI-only): +remote auth show --config +remote status show --config [--data-dir ] +remote probe run --config [--timeout 60] +remote reporting show --config +WxAgent.Tray.exe must be started by double-click; use its 服务设置... window for configuration. doctor [--timeout 30] diagnose --output [--baseline ] [--timeout 60] inspect-ui --output [--window-title ] [--timeout 30] diff --git a/node-agent/WxAgent.Host/RemoteCliCommands.cs b/node-agent/WxAgent.Host/RemoteCliCommands.cs index 235e96d..71a46d2 100644 --- a/node-agent/WxAgent.Host/RemoteCliCommands.cs +++ b/node-agent/WxAgent.Host/RemoteCliCommands.cs @@ -9,6 +9,8 @@ internal static class RemoteCliCommands { if (args.Length < 3) throw Invalid("Remote commands require a group and action."); + if (IsConfigurationCommand(args)) + throw Invalid("Windows Agent configuration is GUI-only; double-click WxAgent.Tray.exe and use 服务设置... ."); var configPath = Path.GetFullPath(Required(args, "--config")); var group = args[1]; var action = args[2]; @@ -193,6 +195,13 @@ internal static class RemoteCliCommands return new { saved = path, accountId = Mask(accountId), chatId = Mask(chatId), type, reporting.ConfigVersion }; } + private static bool IsConfigurationCommand(string[] args) => (args[1], args[2]) switch + { + ("auth", "set" or "clear") => true, + ("reporting", "enable" or "disable" or "account-add" or "account-enable" or "account-disable" or "allow" or "deny") => true, + _ => false + }; + private static ReportingChatType ParseChatType(string value) => value.ToLowerInvariant() switch { "group" => ReportingChatType.Group, diff --git a/node-agent/WxAgent.Service/ServiceOptions.cs b/node-agent/WxAgent.Service/ServiceOptions.cs index adbd215..903e71c 100644 --- a/node-agent/WxAgent.Service/ServiceOptions.cs +++ b/node-agent/WxAgent.Service/ServiceOptions.cs @@ -25,6 +25,7 @@ public sealed class ServiceOptions // Explicitly opt-in for a single, user-authorized Windows validation session. // Production deployments remain read-only unless this local gate is enabled. public bool EnableValidationOperations { get; init; } + public bool PreventAutoLock { get; init; } // Kept only so older service.json files can be loaded and rewritten by the tray. [JsonIgnore] diff --git a/node-agent/WxAgent.Tray/Program.cs b/node-agent/WxAgent.Tray/Program.cs index 724e903..d985987 100644 --- a/node-agent/WxAgent.Tray/Program.cs +++ b/node-agent/WxAgent.Tray/Program.cs @@ -15,22 +15,16 @@ internal static class Program [STAThread] private static void Main(string[] args) { - if (args.Contains("--prevent-auto-lock", StringComparer.OrdinalIgnoreCase)) + if (args.Length != 0) { - PowerPolicy.Apply(); - if (args.Contains("--exit", StringComparer.OrdinalIgnoreCase)) return; + MessageBox.Show("请直接双击 WxAgent.Tray.exe 运行;启动参数和命令行配置不受支持。", "WxAgent", MessageBoxButtons.OK, MessageBoxIcon.Information); + return; } - ApplicationConfiguration.Initialize(); - var configPath = GetOption(args, "--config") ?? Path.Combine(AppContext.BaseDirectory, "service.json"); - using var context = new TrayApplicationContext(Path.GetFullPath(configPath)); - Application.Run(context); - } - private static string? GetOption(string[] args, string name) - { - for (var index = 0; index < args.Length - 1; index++) - if (args[index] == name) return args[index + 1]; - return null; + ApplicationConfiguration.Initialize(); + var configPath = Path.Combine(AppContext.BaseDirectory, "service.json"); + using var context = new TrayApplicationContext(configPath); + Application.Run(context); } } @@ -104,7 +98,7 @@ internal sealed class TrayApplicationContext : ApplicationContext Visible = true, ContextMenuStrip = menu }; - trayIcon.DoubleClick += (_, _) => OpenConsole(); + trayIcon.DoubleClick += (_, _) => OpenConfig(); try { var firstRun = EnsureConfiguration(); @@ -178,7 +172,8 @@ internal sealed class TrayApplicationContext : ApplicationContext Remote = source.Remote, Reporting = source.Reporting, RemoteConfigurationFile = source.RemoteConfigurationFile, - EnableValidationOperations = source.EnableValidationOperations + EnableValidationOperations = source.EnableValidationOperations, + PreventAutoLock = source.PreventAutoLock }; private ServiceOptions ReadOptions() => @@ -191,6 +186,7 @@ internal sealed class TrayApplicationContext : ApplicationContext var loaded = ReadOptions(); loaded.Validate(); options = loaded; + if (loaded.PreventAutoLock) PowerPolicy.Apply(); service = ServiceHost.Build(loaded, new WindowsAgentBackend(new AccountBindingStore(loaded), loaded), logPath: GetLogPath()); try { @@ -363,7 +359,7 @@ internal static class ServiceSettingsEditor { Text = "WxAgent 服务设置", Width = 620, - Height = 355, + Height = 405, StartPosition = FormStartPosition.CenterScreen, MinimizeBox = false, MaximizeBox = false, @@ -377,18 +373,30 @@ internal static class ServiceSettingsEditor var tokenBox = new TextBox { Left = 145, Top = 87, Width = 345, Text = token, MaxLength = 0 }; var copy = new Button { Left = 500, Top = 85, Width = 88, Text = "复制" }; var regenerate = new Button { Left = 145, Top = 123, Width = 105, Text = "重新生成" }; + var validation = new CheckBox + { + Left = 145, Top = 158, Width = 430, + Text = "启用本机验证写操作(仅授权测试机)", + Checked = current.EnableValidationOperations + }; + var preventAutoLock = new CheckBox + { + Left = 145, Top = 187, Width = 430, + Text = "防止自动息屏、睡眠和锁屏", + Checked = current.PreventAutoLock + }; var note = new Label { - Left = 18, Top = 170, Width = 570, Height = 72, - Text = "仅保留一个访问凭据,可直接输入自定义内容(不能为空或包含空白字符,不限制长度)。\n保存后新凭据立即生效,重新生成会使旧凭据失效。本机回环访问不需要凭据。" + Left = 18, Top = 220, Width = 570, Height = 48, + Text = "仅保留一个访问凭据,可直接输入自定义内容(不能为空或包含空白字符)。\n验证写操作只应在明确授权的测试机启用;本机回环访问不需要凭据。" }; var data = new Label { - Left = 18, Top = 246, Width = 570, Height = 24, + Left = 18, Top = 278, Width = 570, Height = 24, Text = $"数据目录:{current.DataDirectory}", AutoEllipsis = true }; - var save = new Button { Left = 370, Top = 285, Width = 105, Text = "保存" }; - var cancel = new Button { Left = 485, Top = 285, Width = 105, Text = "取消" }; + var save = new Button { Left = 370, Top = 330, Width = 105, Text = "保存" }; + var cancel = new Button { Left = 485, Top = 330, Width = 105, Text = "取消" }; copy.Click += (_, _) => { Clipboard.SetText(tokenBox.Text); copy.Text = "已复制"; }; regenerate.Click += (_, _) => { @@ -415,7 +423,9 @@ internal static class ServiceSettingsEditor DataDirectory = current.DataDirectory, Remote = current.Remote, Reporting = current.Reporting, - RemoteConfigurationFile = current.RemoteConfigurationFile + RemoteConfigurationFile = current.RemoteConfigurationFile, + EnableValidationOperations = validation.Checked, + PreventAutoLock = preventAutoLock.Checked }; try { edited.Validate(); } catch (Exception exception) @@ -427,7 +437,7 @@ internal static class ServiceSettingsEditor form.Close(); }; cancel.Click += (_, _) => form.Close(); - form.Controls.AddRange([listenLabel, hostBox, portBox, external, tokenLabel, tokenBox, copy, regenerate, note, data, save, cancel]); + form.Controls.AddRange([listenLabel, hostBox, portBox, external, tokenLabel, tokenBox, copy, regenerate, validation, preventAutoLock, note, data, save, cancel]); form.AcceptButton = save; form.CancelButton = cancel; form.ShowDialog();