zcbot/windows-node/WPF_HOST_REFACTOR.md

41 KiB
Raw Blame History

Windows Node Host 与 WPF 重构方案

状态:阶段 07 已落地,进入阶段 8 发布准备 日期2026-08-31 适用范围:windows-node/Zcbot.WindowsNode 及其测试、打包与运行文档

1. 背景

当前 Windows Node 已完成托盘常驻、节点注册、WebSocket 连接、任务持久化与恢复、Adapter 发现与执行、Workspace 管理、专业软件配置、受管 runtime 安装、本机任务查看、数据目录迁移和 ANSYS 真机验收。它已经从一个简单的内网托盘 MVP 演进为专业软件任务的稳定宿主。

当前实现仍是单个 .NET 10 WinForms WinExe,后台协议与执行能力总体边界正确,但 UI 和 Host 内部职责开始集中:

  • ConfigurationForm.cs 约 1300 行,布局、状态、软件管理、任务展示、数据迁移和 ANSYS 验收混在同一窗口类中。
  • TrayApplicationContext.cs 同时管理托盘、窗口、注册、连接、重连、迁移和安全退出。
  • NodeConnectionLoop.cs 同时承担 WebSocket 会话、心跳、消息解析、任务编排、恢复、取消与终态回报。
  • JobInboxStore.cs 同时承担协议接单、磁盘持久化、状态流转、恢复扫描和 UI 展示读取。
  • SoftwareRuntimeManager.cs 以大体量静态类同时承担软件定义、位置存储、探测、环境注入和 runtime 安装。
  • 注册、下载和上传分别管理 HTTP 客户端,网络错误分类、重试和认证注入缺少统一边界。
  • 现有 Windows Node 测试中有较多源码字符串断言,能够保护若干安全约束,但难以验证真实状态转换和异步生命周期。

本次重构同时处理两个问题:

  1. 将 WinForms 主窗口迁移为 WPF使用 XAML、数据绑定和 MVVM 组织界面。
  2. 将 Node Host 重构为可测试、可组合、与 UI 无关的长期宿主架构。

本次不是服务端协议重写,也不是 Adapter 架构替换。

2. 目标

2.1 用户与运维目标

  • 保持现有节点身份、数据目录、任务、Workspace 和 runtime 原位兼容。
  • 保持解压发布包后直接运行的部署方式。
  • 保持同一 EXE 的托盘、enrollrun --headless 三种入口。
  • 提升 125%、150% 和 200% DPI 下的布局稳定性。
  • 让节点状态、软件状态、任务状态和长时间操作在界面中保持一致。
  • 让新增专业软件主要落在 Adapter 和软件定义,不再扩张单个巨型窗口。
  • 在出现连接、传输、Worker 或恢复故障时提供可定位但不泄露 Token 的本机诊断。

2.2 工程目标

  • 建立唯一 Composition RootGUI 与 headless 共用同一套 Host 组装。
  • 将 UI、应用编排、领域状态、协议传输和基础设施分层。
  • 以明确的 Host 生命周期替代托盘类直接管理连接任务。
  • 以明确的 Job 状态机约束接单、下载、执行、上传、取消和恢复。
  • 统一 HTTP、WebSocket、认证、错误分类和传输重试边界。
  • 统一 Adapter 目录发现、合同加载、软件 probe 和进程监督边界。
  • 对纯逻辑和关键异步流程增加 .NET 行为测试,减少脆弱的源码字符串测试。
  • 每个实施阶段都可构建、可打包、可验收和可回退。

3. 非目标

以下事项不随本轮重构进入实现:

  • 不改服务端 HTTP/WebSocket URL、消息类型、字段语义或认证方式。
  • 不改 node.jsonjobs/workspaces/runtimes/ 的对外文件布局。
  • 不清理、重置或隐式迁移已有节点数据。
  • 不改 Adapter 的 adapter.json + contract + worker + job directory 协议。
  • 不引入 Python GUI、Electron、WebView 或 WinUI 3。
  • 不安装 Windows Service不拆 Service/DesktopRunner 双进程。
  • 不增加自动更新平台、插件市场或在线 Adapter 覆盖。
  • 不增加多 Job 并行或 per-capability slot继续保持整机单执行槽。
  • 不把 Adapter probe、软件语义或具体软件分支移入通用 Host。
  • 不在框架迁移时同时进行大规模视觉重设计。

4. 必须保持的兼容契约

契约 重构后要求
可执行文件 仍为 Zcbot.WindowsNode.exe
GUI 启动 无参数启动托盘;未注册时显示主窗口,已注册时后台连接
CLI enroll 参数、输出标签和退出码不变
Headless run --headless 不初始化 WPF、窗口或托盘
单实例 继续使用 Local\\Zcbot.WindowsNode Mutex
节点身份 继续读取现有 DPAPI LocalMachine 加密 Token
注册表 数据根目录、软件位置和当前账号 Run 项键名不变
数据目录 默认值、环境变量优先级和旧 %ProgramData% 兼容不变
Job 已落盘任务可由新版继续恢复、取消、上传和回报终态
Workspace currentrollback 和 head Job 语义不变
Adapter 继续从 EXE 同级 adapters/*/adapter.json 发现
发布 完整包和独立 Adapter 包的目录结构不变
服务端 无 DB migration、无 API 变化、无部署联动要求

任何需要改变以上契约的实现发现都必须暂停,单独形成兼容设计,不得借重构直接修改。

5. 总体架构

flowchart TD
    Entry["Program / Composition Root"]
    Tray["TrayHost"]
    Wpf["WPF Views"]
    VM["ViewModels"]
    App["Application Services"]
    Host["NodeHost"]
    Session["NodeSession"]
    Jobs["JobCoordinator"]
    Transfers["TransferClient"]
    Adapters["AdapterCatalog / AdapterExecutor"]
    Persistence["Config / Job / Workspace Repositories"]
    Runtime["SoftwareRuntimeService"]
    Server["zcbot HTTP / WebSocket"]
    Workers["Python or EXE Workers"]
    Disk["Existing data root"]

    Entry --> Tray
    Entry --> Host
    Tray --> Wpf
    Wpf --> VM
    VM --> App
    Tray --> App
    App --> Host
    Host --> Session
    Host --> Jobs
    Jobs --> Transfers
    Jobs --> Adapters
    Jobs --> Persistence
    App --> Runtime
    Session --> Server
    Transfers --> Server
    Adapters --> Workers
    Persistence --> Disk
    Runtime --> Disk

5.1 分层规则

Presentation

包含 WPF View、ViewModel、托盘和对话框适配器。

  • 可以引用 Application。
  • 不直接引用 ClientWebSocketHttpClient、Registry、文件系统或进程。
  • XAML code-behind 只处理纯 View 行为,例如 PasswordBox 取值、窗口拖动和焦点。
  • 不在 MainWindow.xaml.cs 中编排节点业务。

Application

包含用户操作和宿主生命周期的用例编排。

  • 注册、重连、清除身份、迁移数据目录、安全退出。
  • 软件位置变更、runtime 安装和固定验收。
  • 组合领域结果为不可变 UI Snapshot。
  • 不依赖 WPF 或 WinForms 类型。

Host / Domain

包含节点会话、Job 状态机、单槽执行、恢复和 Adapter 抽象。

  • 不感知托盘、窗口、按钮或对话框。
  • 不通过任意字符串隐式改变 Job 状态。
  • 关键状态转换经过统一校验和持久化。

Infrastructure

包含 HTTP、WebSocket、JSON、DPAPI、Registry、文件、进程和软件探测实现。

  • 向上提供明确的小接口或服务,不把 Win32/IO 类型扩散到 ViewModel。
  • 原子写、路径约束、日志脱敏和进程树终止在此集中实现。

6. 目标项目结构

第一轮保持单一 Zcbot.WindowsNode.csproj,先形成目录和命名空间边界,避免同时承担程序集拆分、internal 可见性和发布路径变化。完成后再依据依赖图决定是否拆出测试友好的 Core 项目。

Zcbot.WindowsNode/
├── Program.cs
├── App.xaml
├── App.xaml.cs
│
├── Bootstrap/
│   ├── NodeCompositionRoot.cs
│   ├── NodeCommandLine.cs
│   └── SingleInstanceGuard.cs
│
├── Application/
│   ├── NodeApplicationController.cs
│   ├── NodeApplicationSnapshot.cs
│   ├── NodeShutdownDecision.cs
│   ├── DiagnosticService.cs
│   ├── JobMonitorService.cs
│   ├── SoftwareManagementService.cs
│   └── AnsysAcceptanceService.cs
│
├── Host/
│   ├── NodeHost.cs
│   ├── NodeHostSnapshot.cs
│   ├── NodeSession.cs
│   ├── NodeSessionState.cs
│   └── NodeEvent.cs
│
├── Protocol/
│   ├── NodeProtocolClient.cs
│   ├── NodeProtocolCodec.cs
│   ├── ServerMessage.cs
│   ├── NodeMessage.cs
│   └── NodeProtocolException.cs
│
├── Jobs/
│   ├── JobCoordinator.cs
│   ├── JobExecutionGate.cs
│   ├── JobStateMachine.cs
│   ├── JobRepository.cs
│   ├── JobRecoveryService.cs
│   └── JobSnapshot.cs
│
├── Transfers/
│   ├── NodeHttpClient.cs
│   ├── JobInputTransfer.cs
│   ├── JobOutputTransfer.cs
│   └── TransferRetryPolicy.cs
│
├── Adapters/
│   ├── AdapterCatalog.cs
│   ├── AdapterDescriptor.cs
│   ├── AdapterContractLoader.cs
│   ├── AdapterExecutionService.cs
│   └── ProcessSupervisor.cs
│
├── Workspaces/
│   ├── WorkspaceRepository.cs
│   └── WorkspacePromotionService.cs
│
├── Runtime/
│   ├── SoftwareCatalog.cs
│   ├── SoftwareLocationService.cs
│   ├── SoftwareProbeService.cs
│   └── ManagedRuntimeInstaller.cs
│
├── Persistence/
│   ├── NodeConfigStore.cs
│   ├── NodeDataRootService.cs
│   ├── AtomicFile.cs
│   ├── PathGuard.cs
│   └── RotatingNodeLog.cs
│
├── Presentation/
│   ├── Tray/
│   ├── ViewModels/
│   ├── Views/
│   ├── Commands/
│   ├── Converters/
│   └── Themes/
│
└── Compatibility/
    └── persisted model readers retained only where required

目录是目标职责图,不要求通过一次机械移动完成;重构期间优先保证逻辑边界,最后再统一物理目录。

7. Host 核心设计

7.1 Composition Root

NodeCompositionRoot 是唯一对象组装入口:

  • 解析一次数据根目录并生成 NodePaths
  • 创建唯一 NodeConfigStoreJobRepositoryWorkspaceRepositoryAdapterCatalog
  • 按当前节点配置创建共享 NodeHttpClientNodeProtocolClient
  • 创建 JobCoordinatorNodeSessionNodeHost
  • GUI 与 headless 只决定 Presentation 和生命周期宿主,不各自复制 Host 构造逻辑。

不引入通用依赖注入容器;当前规模使用显式构造函数组装更便于审计、打包和故障定位。

7.2 NodeHost 生命周期

NodeHost 对外只暴露少量稳定操作:

Task RunAsync(CancellationToken cancellationToken);
Task ReconnectAsync(CancellationToken cancellationToken);
Task<NodeShutdownResult> StopAsync(NodeShutdownMode mode, CancellationToken cancellationToken);
NodeHostSnapshot Current { get; }
event EventHandler<NodeHostSnapshot>? SnapshotChanged;

生命周期约束:

  • RunAsync 是结构化并发根拥有会话、恢复、Job 和传输子任务。
  • 所有子任务都可追溯到 Host CancellationToken不允许 fire-and-forget Worker 管线。
  • 重连先停止接收 offer再等待本地任务和上传到达安全点之后替换会话。
  • 正常退出与取消任务退出使用同一 StopAsyncGUI 和 headless 不复制收尾逻辑。
  • StopAsync 幂等;重复点击退出或系统注销不会启动第二条取消链。
  • Snapshot 是不可变值UI 只消费 Snapshot不读取 Host 内部集合。

7.3 WebSocket 会话拆分

NodeConnectionLoop 提取:

  • NodeSession连接、hello、心跳、接收循环和退避重连。
  • NodeProtocolCodecJSON 消息的严格解析、类型分发和序列化。
  • NodeProtocolClient:并发安全发送和 WebSocket 关闭语义。
  • JobCoordinator:处理 offer、cancel 和导出等业务消息。

要求:

  • 一个会话只拥有一个接收循环。
  • 所有发送通过同一发送锁,避免并发 SendAsync
  • 认证拒绝、握手 HTTP 错误、网络中断和协议错误继续保持不同状态。
  • 未知消息按现有兼容策略处理,不得把解析异常误报为身份失效。
  • 心跳携带的能力、health、slot 和合同摘要来自同一个原子 Snapshot。

7.4 Job 状态机

新增 JobStateMachine 作为内部唯一状态转换入口,覆盖:

offered
  -> accepted
  -> downloading
  -> ready
  -> software_running
  -> promoting_workspace
  -> uploading_preview / uploading_output
  -> succeeded

任意可取消阶段 -> cancelling -> cancelled
任意执行阶段   -> failed
进程重启       -> recovered according to persisted markers

具体落盘字符串和 JSON 字段保持现状;状态机首先包裹现有格式,不强制迁移历史文件。

要求:

  • offer 仍然先持久化并 flush/原子替换,再回复 accept。
  • Job ID、lease ID 和 request digest 一致性校验保持不变。
  • 终态不可逆;重复终态消息必须幂等。
  • 单执行槽由本机 JobExecutionGate 再次强制,不只依赖云端 slot。
  • Workspace promotion、预览上传和显式导出分别建模不能因上传重试重复执行专业软件。
  • 进程重启恢复只依赖已落盘事实,不依赖 UI 内存状态。

7.5 Job 持久化拆分

JobInboxStore 拆为三个职责:

  • JobRepository:原子读写 Job、marker 和 terminal。
  • JobRecoveryService:启动扫描和恢复决策。
  • JobMonitorService:面向 UI 生成只读展示 Snapshot。

统一 AtomicFile

  • 同目录临时文件。
  • 写入、flush、关闭后原子替换。
  • 原文件存在时保持当前替换语义。
  • 不用跨卷 rename。
  • 临时文件命名不与 Worker 协议文件冲突。

统一 PathGuard

  • 所有 Job、Workspace、Adapter 和 runtime 派生路径必须归一化后验证仍位于预期根目录。
  • 不把请求内文件名直接拼入本机路径。
  • 保持合同内输出 ID 与实际文件 manifest 的现有校验。

7.6 传输层

创建每个已加载节点配置唯一的 NodeHttpClient

  • 统一 BaseAddress、Bearer Token、User-Agent、默认超时和错误摘要。
  • 输入下载和输出上传复用连接池。
  • 大文件继续流式传输,不整体读入内存。
  • 重试只覆盖可安全重放的阶段,上传继续依赖服务端幂等校验。
  • 认证失败不进入普通网络重试。
  • 日志不记录 Authorization Header、Node Token、注册码或签名 URL 查询串。

JobInputTransferJobOutputTransfer 只负责传输;是否重试、何时回报终态由 JobCoordinator 决定。

7.7 Adapter 与进程监督

将当前 Adapter 发现和执行拆为:

  • AdapterCatalog:启动时一次发现、合同加载和描述符缓存。
  • AdapterContractLoadermanifest、合同路径、Schema 和 SHA-256 校验。
  • AdapterExecutionService:准备 Job 目录并调用固定 Adapter。
  • ProcessSupervisor:启动、标准流采集、超时、取消、完整进程树终止和退出确认。

安全边界保持:

  • Worker 入口必须来自已安装 manifest不来自任务请求。
  • 入口归一化后必须位于对应 Adapter 目录。
  • Python 解释器必须来自固定受管 runtime 解析。
  • 请求不能注入命令行、环境变量、工作目录或本机路径。
  • 进程退出后才读取 terminal取消后必须等待完整进程树退出。
  • Host 不解析 Origin、ANSYS、Blender 业务语义。

7.8 Workspace

WorkspaceRepository 负责现有 metadata 和目录读取,WorkspacePromotionService 负责:

  • 校验 source head。
  • 在临时目录准备下一版。
  • 成功后更新 rollbackcurrent
  • 失败时保持旧 current 可继续使用。
  • 只保留一份 rollback。

重构不得增加按 Job 永久保存工程副本,也不得在 Node 间搬迁 Workspace。

7.9 软件与 runtime 管理

SoftwareRuntimeManager 拆为:

  • SoftwareCatalog:固定的软件和 runtime 定义。
  • SoftwareLocationService:注册表配置、自动检测和版本来源。
  • SoftwareProbeService:通过 Adapter probe 汇总最终 health。
  • ManagedRuntimeInstaller:固定 requirements、临时目录安装、验证和原子替换。

要求:

  • 软件卡继续按 runtime_id 归并多个 capability。
  • 环境变量优先级和现有兼容名称不变。
  • runtime 有活动任务时继续拒绝替换。
  • 安装命令、镜像地址和 requirements 不接受任务请求控制。
  • Origin 软件位置识别与 COM 健康继续分离。
  • ANSYS 执行门和固定验收继续由 Adapter/验收脚本给出事实结果。

8. WPF Presentation 设计

8.1 框架选择

  • 继续使用 C# 和 .NET 10。
  • 项目启用 WPF。
  • 暂时保留 WinForms 引用,仅复用成熟的 NotifyIcon
  • 不引入第三方 MVVM 或主题框架;先使用小型 ObservableObjectRelayCommandAsyncCommand 和资源字典。
  • 首版使用浅色主题,颜色、字体、间距和控件状态全部抽为资源。

8.2 View 与 ViewModel

MainWindow
├── OverviewView / OverviewViewModel
├── SoftwareView / SoftwareViewModel
│   ├── SoftwareCardViewModel[]
│   └── AnsysAcceptanceViewModel
├── JobsView / JobsViewModel
└── SettingsView / SettingsViewModel

ViewModel 只接收 Application Service 和不可变 Snapshot不直接 new Adapter Registry、Job Store 或路径对象。

8.3 状态绑定

以下状态由 Snapshot 或专用 ViewModel 字段统一驱动:

  • 节点连接状态、颜色、说明和操作可用性。
  • 注册卡是否显示。
  • 软件位置、runtime、probe 和 capability 状态。
  • runtime 安装、验收和数据迁移进度。
  • Job 列表、选中项、详情和刷新状态。
  • 退出期间全局操作禁用。

不允许同一状态同时由多个页面各自推断。

8.4 托盘与窗口生命周期

TrayHost 只处理:

  • 创建和释放 NotifyIcon。
  • 将 Host Snapshot 映射为图标、悬浮文本和菜单摘要。
  • 打开、隐藏和激活 WPF 主窗口。
  • 收集退出选择并调用 NodeApplicationController.StopAsync

活动任务退出的三种选择、二次确认和等待提示保持现有语义。业务取消与进程收尾不在 TrayHost 内实现。

8.5 可访问性与 DPI

  • 使用设备无关布局,不写依赖当前字体像素测量的固定页面宽度。
  • 关键页面在 760×560 最小窗口和 100%200% DPI 下可访问。
  • 长路径、长错误和任务标题允许换行、截断并提供完整详情。
  • 设置合理的 Tab 顺序、AccessKey、默认按钮和取消行为。
  • 不仅使用颜色表达在线、警告和失败状态。
  • 长时间操作必须有文本状态、进度反馈和可用时的取消入口。

9. 诊断与可观测性

在不引入远程日志平台的前提下,增加受控本机 Host 日志:

  • 路径位于现有 data root 的 logs/
  • 使用 ASCII 级别标签:[INFO][WARN][ERR]
  • 单文件有明确大小上限并只保留有限轮转文件。
  • 记录会话阶段、Job ID、capability、Adapter 版本、Worker PID、传输阶段和 HRESULT/异常类型。
  • 不记录 Node Token、注册码、Authorization Header、完整签名 URL 或用户输入文件内容。
  • “复制诊断信息”继续输出可粘贴摘要,不默认复制完整日志。

NodeHostSnapshot 至少包含:

  • 当前节点状态和状态变化时间。
  • 当前会话 ID 或本地连接代次,不包含凭据。
  • 活动 Job、当前阶段和开始时间。
  • 待恢复/待上传数量。
  • Adapter 数量、能力摘要和最近 probe 时间。
  • data root 和版本信息。

10. 测试策略

10.1 Characterization 测试

重构前固定当前行为:

  • CLI 参数、stdout/stderr 标签和退出码。
  • 未注册、在线、身份失效和普通断线状态差异。
  • offer 先落盘后 accept。
  • 重复 Job digest 校验。
  • 终态与上传重试幂等。
  • Workspace promotion 与 rollback。
  • 活动任务退出的等待和取消路径。
  • 数据目录迁移的停止、复制、校验和切换顺序。

10.2 .NET 单元测试

新增 Zcbot.WindowsNode.Tests,优先测试:

  • JobStateMachine 合法和非法转换。
  • NodeHost 启停、重复停止和重连屏障。
  • NodeProtocolCodec 消息分类及错误处理。
  • JobRecoveryService 对各持久化阶段的恢复决策。
  • TransferRetryPolicy 对认证、网络、服务端和取消错误的分类。
  • AdapterContractLoader 的路径、manifest、版本与合同校验。
  • ViewModel Snapshot 映射、Command 可用性和异步失败恢复。

测试使用临时目录和可控的 HTTP handler/协议替身;不连接 .env 中的数据库或生产服务。

10.3 集成测试

  • 本地假 WebSocket 服务完成 hello、心跳、offer、cancel 和重连。
  • 固定测试 Worker 验证启动、超时、取消和进程树回收。
  • 临时 data root 验证重启恢复、原子写和 Workspace promotion。
  • framework-dependent 与 self-contained 两种发布包完成启动烟测。
  • 发布包验证 Adapter、合同、验收资产和 requirements 完整。

10.4 UI 验收

  • 未注册首次启动。
  • 已注册后台启动和托盘显示。
  • 托盘双击、窗口隐藏和恢复。
  • 注册、重连、清身份和复制诊断。
  • 软件自动检测、手选、清除和 runtime 安装。
  • ANSYS 固定验收、取消、通过报告和执行门。
  • 本机任务刷新、选中详情和重启后恢复。
  • 活动任务等待退出、取消退出和返回。
  • 100%、125%、150%、200% DPI。

10.5 现有 Python 源码测试调整

保留适合静态审计的测试:

  • 项目目标框架和发布参数。
  • 不暴露任意执行原语。
  • Adapter 与合同是否完整打包。
  • Token 不以明文模型字段或日志字段出现。
  • 独立 Adapter 包不依赖重建 Node。

将控件名称、WinForms 布局字符串、按钮宽度等断言替换为 .NET 行为测试或 WPF 结构测试,避免测试阻止内部文件拆分。

11. 实施阶段

阶段 0基线与保护网

  • 固化功能、协议、持久化和发布包基线。
  • 增加关键生命周期 Characterization 测试。
  • 记录真机 DPI 与托盘行为。
  • 不改变生产行为。

完成门槛:现有 build、专项测试、打包和启动烟测全部通过。

阶段 1Composition Root 与应用控制器

  • 引入唯一 NodeCompositionRoot
  • 从托盘类提取注册、连接、重连、迁移和退出编排。
  • GUI 与 headless 共用 Host 构造路径。
  • WinForms 界面保持不变。

完成门槛:现有界面和 CLI 行为完全等价。

阶段 2Job 持久化与状态机

  • 提取 JobRepositoryJobStateMachineJobRecoveryService
  • 集中原子文件和路径约束。
  • 保持现有落盘格式,验证新版能恢复旧 Job。
  • 增加状态机和恢复测试。

完成门槛:旧 Job 样本可恢复,终态与 Workspace 不回退。

阶段 3会话、协议与传输

  • 拆分 NodeSessionNodeProtocolCodecNodeProtocolClient
  • 引入共享 NodeHttpClient
  • 统一错误分类、认证注入和安全重试。
  • JobCoordinator 接管消息业务编排。

完成门槛假服务端重连、offer、cancel、上传重试和身份拒绝测试通过。

阶段 4Adapter、进程与 runtime

  • 提取 AdapterCatalogAdapterExecutionServiceProcessSupervisor
  • 拆分软件位置、probe 和 runtime 安装。
  • 统一活动任务执行门和取消收尾。
  • 不改 Adapter 文件或合同。

完成门槛Origin、ANSYS、Blender 现有 probe、执行、取消和打包测试通过。

阶段 5WPF Shell 与托盘

  • 启用 WPF建立 App、MainWindow、资源字典和 ViewModel 基础设施。
  • 保留 WinForms NotifyIcon。
  • 完成无参数 GUI、CLI 和 headless 启动分流。
  • 先建立空页面与完整生命周期,不删除旧窗口。

完成门槛:单实例、后台启动、显示/隐藏和安全退出通过。

阶段 6逐页迁移

按以下顺序迁移:

  1. 节点概览与首次注册。
  2. 运行设置与数据目录迁移。
  3. 专业软件卡和 runtime 管理。
  4. ANSYS 固定验收。
  5. 本机任务列表和详情。

完成门槛:每一页迁移后均完成行为等价检查,不等待最后一次性验收。

阶段 7移除 WinForms 主窗口与加固

  • 删除 ConfigurationForm 和旧 TrayApplicationContext
  • 保留 NotifyIcon 所需的最小 WinForms 依赖。
  • 清理重复状态、静态 Service Locator 和临时兼容代码。
  • 完成本机结构化日志与诊断摘要。
  • 完成全量专项测试和发布包真机烟测。

完成门槛:源码中没有第二套可达 UI 或重复 Host 生命周期。

阶段 8文档与发布准备

  • 架构落地后把稳定决策同步到根 DESIGN.md
  • 如运行方式、诊断路径或部署步骤有变化,同步 RUN.mdwindows-node/README.md
  • 阶段性成果更新 PROGRESS.md
  • WPF 稳定前不提升版本号,不创建已发布 Changelog 版本。
  • 准备上线时再通过独立 release commit 更新版本和用户版 Changelog。

12. 建议提交拆分

每个提交只包含一个可验证的逻辑变化:

  1. test(windows-node): characterize host lifecycle
  2. refactor(windows-node): add shared composition root
  3. refactor(windows-node): extract job state and persistence
  4. refactor(windows-node): separate session and protocol handling
  5. refactor(windows-node): unify job transfers
  6. refactor(windows-node): extract adapter process supervision
  7. refactor(windows-node): split software runtime services
  8. feat(windows-node): add wpf shell and tray host
  9. feat(windows-node): migrate overview and settings views
  10. feat(windows-node): migrate software and acceptance views
  11. feat(windows-node): migrate local job monitor
  12. refactor(windows-node): remove legacy winforms window
  13. test(windows-node): cover wpf and host integration
  14. docs(windows-node): document host and wpf architecture

实施中可根据实际依赖合并相邻提交,但不得将 Host 重构、UI 视觉调整和发布版本提升混入同一提交。

13. 风险与控制

风险 控制措施
重连时重复启动任务 Host 统一拥有会话代次和单槽执行门
退出后残留 Worker 所有进程归 ProcessSupervisor,停止时等待完整树退出
上传重试导致重复执行 执行终态与传输状态分离,传输只重放幂等上传
后台线程更新 WPF 集合 Snapshot 在 Dispatcher 上映射到 ObservableCollection
新版无法恢复旧 Job 不改落盘字段,使用历史样本做恢复测试
数据迁移期间重新接单 Controller 先停调度并等待安全屏障,再执行迁移
runtime 安装覆盖活动环境 本机执行门检查活动 Job临时安装验证后原子替换
Token 泄露到日志或 UI 集中认证客户端、脱敏器和诊断字段 allowlist
WPF 与 WinForms 类型冲突 WinForms 仅限 Tray 命名空间并使用完整类型名
重构范围失控 非目标列表和阶段完成门槛作为 review gate

14. 回退策略

  • 整个改动在独立功能分支完成WPF 稳定前不让线上用户接触。
  • 每个阶段保持可构建Host 抽取阶段继续由旧 WinForms UI 驱动。
  • WPF 迁移期间旧窗口可作为开发对照,但最终合并前必须只有一套可达 UI。
  • 发布前保留上一版完整 Node 程序包;回退只替换程序目录,不修改 data root。
  • 因持久化格式保持兼容,回退旧 EXE 后仍能读取原有身份、Job 和 Workspace。
  • 若实施中确需新增持久化字段,必须做到旧版本忽略、新版本可缺省,且在本方案中补充独立兼容说明。

15. 完成定义

满足以下条件才视为重构完成:

  • WinForms ConfigurationForm 已移除,主界面全部使用 WPF。
  • WinForms 仅用于 NotifyIcon 或已明确记录的必要互操作。
  • GUI 和 headless 从同一个 Composition Root 获得 Node Host。
  • NodeConnectionLoop 的会话、协议和 Job 编排职责已拆分。
  • JobInboxStore 的持久化、恢复、状态机和 UI 展示职责已拆分。
  • SoftwareRuntimeManager 的定义、位置、probe 和安装职责已拆分。
  • 关键 Host 生命周期和 Job 状态转换有真实 .NET 行为测试。
  • 旧节点身份、历史 Job 和 Workspace 在新版测试样本中可继续使用。
  • Origin、ANSYS、Blender Adapter 无需修改即可运行。
  • framework-dependent 与 self-contained 发布包均通过构建和启动烟测。
  • 真机托盘、DPI、runtime 安装、任务执行、取消、上传和安全退出验收通过。
  • DESIGN.mdRUN.mdPROGRESS.md 和 Windows Node README 已按实际落地范围同步。

16. 首个实施切片

建议第一个开发切片只做以下内容:

  1. 增加 Host 生命周期 Characterization 测试。
  2. 引入 NodeCompositionRoot,消除 GUI 与 headless 的重复构造路径。
  3. 提取 NodeApplicationController,从托盘类移出注册、重连、迁移和退出编排。
  4. 让现有 WinForms UI 继续驱动新的 Controller。
  5. 完成 build、专项测试、打包和启动烟测。

该切片不启用 WPF、不移动 Job 格式、不改 Adapter也不改变用户可见界面。它先建立后续 Host 重构和 WPF 迁移共同依赖的稳定边界,风险最低、验证价值最高。

16.1 实施记录2026-08-31

首个切片已完成:新增 Zcbot.WindowsNode.Tests 与 Host 生命周期行为测试;引入唯一 NodeCompositionRoot 和 UI 无关的 NodeApplicationControllerGUI 与 run --headless 已共用组装入口;注册、重连、清除身份、迁移和安全退出编排已从托盘类移入 Controller。现有 WinForms 主窗口、协议、Job/Workspace 落盘格式、Adapter 和发布目录均保持不变。

验证已覆盖未注册启动、重连收尾屏障、幂等停止、取消退出、迁移失败重启和清身份收尾;.NET 测试、既有 Windows Node 源码专项测试、solution build、framework-dependent 打包及 GUI/headless 启动烟测通过。下一切片进入阶段 2提取 Job 持久化、状态机与恢复决策。

16.2 阶段 2 实施记录2026-08-31

Job 持久化、状态和恢复边界已提取:JobRepository 直接负责既有 request/state/terminal/marker 目录的读取与原子写,JobStateMachine 约束正常单向转换并以显式 Recovery 模式兼容重启后的输入复验和历史成功任务续传,JobRecoveryService 将落盘事实分类为续执行、续上传、重放终态和无需动作。AtomicFilePathGuard 集中保持同目录临时文件、flush 后替换和 Job 派生路径不越界;JobInboxStore 暂时保留 offer 校验、旧调用薄入口和 UI 展示读取,后续由协议与 JobMonitorService 切片继续收窄。

新增测试覆盖完整状态图、非法回退、终态不可重开、恢复例外、旧 request/state 样本无迁移读取、终态幂等、offer 先持久化及 digest/lease 重放、Workspace promotion/rollback、原子替换和路径穿越拒绝。40 项 .NET 行为测试、25 项既有源码专项、格式检查、Release 发布包及 headless 启动烟测通过;下一切片进入阶段 3会话、协议与传输拆分。

16.3 阶段 3 实施记录2026-08-31

会话、协议和 Job 编排已从原连接循环分离:NodeSession 负责连接、hello、心跳、单接收循环与退避重连NodeProtocolCodec 保持既有消息信封并允许未知消息按原策略忽略,NodeProtocolClient 集中处理文本分片、1 MiB 上限、身份关闭语义和发送锁;JobCoordinator 接管 offer、cancel、export、恢复回报及本地执行流水线新的 NodeConnectionLoop 只组合并收尾两者。

输入下载和输出上传现共用每份已加载配置唯一的 NodeHttpClient,统一 BaseAddress、Bearer、Node ID 与 User-Agentlease/digest 保持逐请求 Header避免共享 Header 竞争。TransferRetryPolicy 将认证、瞬时、永久和取消错误分开,只有无请求体的 GET 下载执行有界自动重试,输出上传继续依赖既有服务端幂等确认与落盘恢复,不会因传输重试重新执行专业软件。新增假 WebSocket/HTTP handler 测试覆盖信封兼容、未知消息、分片接收、并发发送、4003 身份拒绝、断线重连、认证不重试和瞬时下载重试57 项 .NET 行为测试、25 项源码专项、格式与 diff 检查、Release 发布包及隔离数据目录 headless 烟测通过。下一切片进入阶段 4拆分 Adapter、进程监督与 runtime。

16.4 阶段 4 实施记录2026-08-31

Adapter 发现、合同、执行和进程边界已拆分:AdapterCatalog 以线程安全 Lazy 在启动期一次发现并缓存已安装描述符,AdapterContractLoader 负责 manifest 白名单、版本/runtime、相对路径边界、合同 capability、Workspace 输出、JSON Schema、入口扩展名和 SHA-256AdapterExecutionService 保留既有 Job 目录协议、终态读取、Workspace promotion/restore 和错误码,所有 capability 共享 JobExecutionGate,不再只依赖云端 slot。ProcessSupervisor 统一固定进程启动、受限标准流采集、取消、完整进程树终止和 30 秒退出确认;对不存在 Job 的取消不再创建遗留 CTS。

软件与 runtime 已拆为 SoftwareCatalogSoftwareLocationServiceSoftwareProbeServiceManagedRuntimeInstaller。Origin/ANSYS/Blender 固定定义、兼容环境变量、注册表与标准目录检测顺序保持不变probe 保持 30 秒缓存并可在 runtime 更新后显式失效;安装器仍只接受内置 requirements 和固定 Python 3.12 发现方式,在临时目录验证后原子替换并保留失败 rollback同时在入口机械拒绝活动执行。新增行为测试覆盖已打包 Adapter 合同加载、跨 capability 单槽、等待取消、软件目录定义和 probe 缓存失效62 项 .NET 行为测试、122 项 Origin/ANSYS/Blender/合同/Node 专项、格式与 diff 检查、Release 发布包及隔离数据目录 headless 烟测通过。下一切片进入阶段 5建立 WPF Shell、ViewModel 基础设施及 NotifyIcon 互操作。

16.5 阶段 5 实施记录2026-08-31

GUI 入口已切换为 WPF Application 消息循环,新增 MainWindow、Light 资源字典、ObservableObject、同步/异步 Command 和 MainWindowViewModel。主窗口提供节点状态总览、隐藏到托盘、安全退出及经典配置入口;关闭窗口只隐藏,不终止后台节点。新的 TrayHost 通过显式 WinForms 类型别名管理 NotifyIcon,把 Controller 状态切回 WPF Dispatcher延续托盘双击、重连、三选一活动任务退出和取消二次确认。尚未迁移的注册、专业软件、验收、本机任务和数据目录功能仍由按需创建的 ConfigurationForm 承担,阶段 8 完成前保持可达。

CLI 参数分支仍在创建任何 WPF 对象前执行,run --headless、单实例互斥体、Composition Root 和 Controller 生命周期保持原语义。新增 ViewModel 状态映射、命令转发、异步命令防重入与异常报告测试66 项 .NET 行为测试、188 项 Origin/ANSYS/Blender/合同/Node 专项、格式与 diff 检查、framework-dependent Release 包、隔离 GUI 消息循环及 headless 返回码烟测通过。下一切片进入阶段 6迁移节点概览、注册与运行设置页面。

16.6 阶段 6 第一切片实施记录2026-08-31

节点概览、首次注册和运行设置基础能力已迁入 WPF。MainWindowViewModel 现在持有页面导航、身份摘要、注册字段、操作反馈及 Command 可用状态,通过事件边界复用 Controller 的注册、重连和清身份流程;一次性注册码由 PasswordBox 同步到 ViewModel注册成功或身份状态刷新后立即清空。清身份继续二次确认注册继续使用 EnrollOptions 的 URL、名称和注册码校验。登录自启动经 IStartupRegistration 接口调用当前账号 Run 项,权限或 IO 失败会保留原开关状态并展示错误。运行设置展示实际 data root迁移按钮暂时进入经典配置以继续使用已有活动任务检查、固定磁盘校验、复制摘要和重启流程。

专业软件、runtime、ANSYS 验收、本机任务和数据目录迁移尚未迁入 WPF因此本记录不把阶段 6 标为整体完成。新增测试覆盖 WPF 页面导航、身份映射、注册参数转发、自启动成功与失败回滚,源码契约确认 WPF 不直接依赖 Controller69 项 .NET 行为测试、189 项 Origin/ANSYS/Blender/合同/Node 专项、格式与 diff 检查、framework-dependent Release 包及隔离 GUI/headless 烟测通过。下一切片迁移专业软件卡与 runtime 管理。

16.7 阶段 6 第二切片实施记录2026-08-31

专业软件卡和 runtime 管理已迁入 WPF。新的 SoftwareManagementService 组合 SoftwareCatalogSoftwareLocationService、Adapter probe 和 ManagedRuntimeInstaller,向 Presentation 只暴露不可变快照与受控操作;SoftwarePageViewModel 在用户进入页面后异步刷新 Origin、ANSYS、Blender 卡片,支持选择严格匹配的 EXE/安装根目录、清除账号级配置后恢复自动检测、安装或更新隔离 runtime以及显式取消安装。文件和目录对话框、安装确认仍封装在 Tray WinForms 互操作层WPF ViewModel 不读取注册表、不创建进程,也不接触任意命令。

软件快照直接读取 AdapterCatalog.HasActiveExecution,全机任一 Job 执行时均禁用 runtime 安装;即使 UI 快照过期,安装器入口仍会二次拒绝活动执行。安装继续固定 Python 3.12、内置 requirements 和镜像配置,在临时目录验证后原子切换,取消时终止完整安装进程树,失败保持原 runtime。新增测试覆盖软件卡刷新、位置保存与清除、安装确认/结果、活动任务禁用和取消收尾73 项 .NET 行为测试、190 项 Origin/ANSYS/Blender/合同/Node 专项、格式与 diff 检查、framework-dependent Release 包及隔离 GUI/headless 烟测通过,未执行真实安装或启动专业软件。下一切片迁移 ANSYS 固定验收。

16.8 WPF UI 打磨记录2026-08-31

在不改变页面命令和 Host 生命周期的前提下Light 主题补齐集中颜色、表面、边框、状态和间距 token并用 WPF ControlTemplate 统一按钮与侧栏导航的圆角、悬停、按下、禁用和键盘焦点状态。侧栏使用单选视觉的 ToggleButton 显示当前页,页头增加实时节点状态徽标,概览卡增加同源状态色带,软件卡增加位置可用性徽标和安装忙碌进度;操作区改用 WrapPanel在 760 px 最小窗口下可自动换行。操作结果改为独立提示条,仅在有消息时占位,减少与底部常驻说明的竞争。

新增 UI 结构契约覆盖导航选中绑定、状态 tone、响应式操作区、忙碌反馈和主题资源既有 ViewModel 测试补充状态色与提示可见性73 项 .NET 行为测试、191 项 Origin/ANSYS/Blender/合同/Node/UI 专项、格式与 diff 检查、framework-dependent Release 包及隔离 GUI/headless 烟测通过。纯视觉改动未改变 CLI、托盘、注册、runtime 安装或持久化契约。

16.9 阶段 6 第三切片实施记录2026-08-31

按主导航可用性优先,将本机任务迁移提前到 ANSYS 固定验收之前。JobMonitorServiceJobInboxStore 接管面向 UI 的只读快照生成,继续合并 request、state、terminal、cloud-terminal 和 upload-complete 落盘事实,不改变既有 Job 目录或恢复协议。新的 JobPageViewModel 负责状态中文映射、执行时长、摘要、选中详情与按 Job ID 保持选择WPF 页面以只读表格展示最近 50 条记录,进入页面立即刷新,并在窗口可见且任务页激活时每秒刷新。侧栏“本机任务”不再打开经典配置。

新增测试覆盖快照刷新、状态格式、读取失败、刷新后选择保持,以及真实 WPF 资源加载后的四页互斥可见性和标题绑定94 项 .NET 行为测试、28 项 Windows Node 源码专项、格式与 diff 检查通过。阶段 6 尚余 ANSYS 固定验收和数据目录完整迁移,完成后再删除经典任务页及其 WinForms 定时器,避免迁移期间影响现有回退入口。

16.10 阶段 6 完成记录2026-08-31

ANSYS 固定验收和数据目录完整迁移已进入 WPF。AnsysAcceptanceService 固定绑定已打包的 ansys.mechanical.static_structural@v2 验收入口ViewModel 提供报告目录选择、长时进度、显式停止、通过报告校验和机器级执行门;执行门仍要求本次报告 passed=true、用户确认许可证已释放,并在权限不足时保持关闭。DataRootPageViewModel 通过 DataRootManagementService 检查环境变量托管与未完成任务,目标规范化和二次确认后只调用 Controller 的迁移编排,继续保持停连、复制、逐文件 SHA-256 校验、成功后保存新根目录与失败重连回滚。

16.11 阶段 7 完成记录2026-08-31

诊断摘要提取为 DiagnosticService 并由 WPF 概览页复制,内容继续排除 Node Token。删除 ConfigurationForm 和旧 TrayApplicationContext托盘菜单不再暴露经典配置入口WinForms 仅保留 NotifyIcon、固定文件/目录选择、确认框和剪贴板互操作。Presentation 不直接持有 Controller不解析 Job JSON不访问注册表也不创建 Worker117 项 .NET 行为测试、28 项 Windows Node 源码专项、格式与 diff 检查通过。下一步进入阶段 8 发布准备与目标机 DPI/GUI 烟测。