feat: make Windows Agent configuration GUI-only
Build web service image / build (push) Successful in 40s
Build web service image / build (push) Successful in 40s
This commit is contained in:
@@ -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
|
||||
|
||||
+5
-15
@@ -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='<test-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": "<remote.json绝对路径>"` 后启动 `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、窗口句柄和未确认的运行时身份都应视为验收失败;手工验收不得改写微信数据库或绕过安全机制。
|
||||
|
||||
@@ -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 客户端使用:
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
## 运行边界
|
||||
|
||||
- 可通过 `WxAgent.Host serve --config <file>` 启动;安装版使用托盘程序,服务设置和唯一 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`/`::` 作为工作台地址。
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
>
|
||||
> 状态:规划,尚未实施或验收。用户已确认目标为“浏览器 Web UI + MCP 服务”。
|
||||
> 本次交付仅为开发计划,不启动服务、不执行微信写操作。
|
||||
> 当前运行约束:Windows Agent 仅由双击 `WxAgent.Tray.exe` 启动;本机配置使用“服务设置...”窗口,命令行 `serve` 和任何配置修改入口均不再提供。下文早期 `serve` 仅保留为历史规划,不是当前运行命令。
|
||||
> 范围变化:原第一阶段排除正式 UI;本文规划其后的控制面阶段。用户已确认 HTTP/Web UI 与 MCP 支持外部访问且共用 Token,不要求 HTTPS,不实现服务端人工审批;不改变 Windows Service、协议及数据库安全边界。
|
||||
|
||||
## 1. 目标与基线
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
> 版本:0.5
|
||||
> 日期:2026-09-11
|
||||
> 状态:后续阶段计划
|
||||
>
|
||||
> 当前入口约束:Windows Agent 只允许双击 `WxAgent.Tray.exe`,本机配置通过“服务设置...”窗口完成;`WxAgent.Host` 仅保留只读/诊断 CLI,任何命令行配置修改均禁用。本文中“本地 CLI 配置”属于历史计划措辞,不是当前可执行入口。
|
||||
|
||||
## 1. 目标
|
||||
|
||||
|
||||
@@ -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 = '<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 <verified-account-id>
|
||||
|
||||
WxAgent.Host.exe remote reporting enable --config $cfg
|
||||
WxAgent.Host.exe remote reporting account-add --config $cfg --account <verified-account-id>
|
||||
WxAgent.Host.exe remote reporting account-enable --config $cfg --account <verified-account-id>
|
||||
```
|
||||
|
||||
内网 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 <verified-account-id> `
|
||||
--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 <verified-account-id> `
|
||||
--type private `
|
||||
--chat-id <verified-private-id> `
|
||||
--identity-verified
|
||||
|
||||
WxAgent.Host.exe remote reporting allow `
|
||||
--config $cfg `
|
||||
--account <verified-account-id> `
|
||||
--type group `
|
||||
--chat-id <verified-group-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` 双击,不是命令行。
|
||||
|
||||
部署完成后至少执行:
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -25,20 +25,8 @@ try
|
||||
}
|
||||
|
||||
if (args[0] == "serve")
|
||||
{
|
||||
ValidateOptions(args, 1, ["--config"], []);
|
||||
var configPath = Path.GetFullPath(GetRequiredOption(args, "--config"));
|
||||
var serviceOptions = JsonSerializer.Deserialize<ServiceOptions>(
|
||||
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 <service.json>
|
||||
remote auth show|set|clear --config <remote.json>
|
||||
auth set options: --address <url> --token <token> | --token-file <path> --node <id>
|
||||
[--server-ca-file <pem>] [--client-certificate-file <pem> --client-certificate-key-file <pem>]
|
||||
remote status show --config <remote.json> [--data-dir <dir>]
|
||||
remote probe run --config <remote.json> [--timeout 60]
|
||||
remote reporting show|enable|disable|account-add|account-enable|account-disable|allow|deny --config <remote.json>
|
||||
WxAgent.Host commands (diagnostics/read-only only; configuration is GUI-only):
|
||||
remote auth show --config <remote.json>
|
||||
remote status show --config <remote.json> [--data-dir <dir>]
|
||||
remote probe run --config <remote.json> [--timeout 60]
|
||||
remote reporting show --config <remote.json>
|
||||
WxAgent.Tray.exe must be started by double-click; use its 服务设置... window for configuration.
|
||||
doctor [--timeout 30]
|
||||
diagnose --output <dir> [--baseline <ui-tree.json>] [--timeout 60]
|
||||
inspect-ui --output <path> [--window-title <title>] [--timeout 30]
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user