# PATCH-NOTES:合入对面 windows-runner 的集成指引 本目录是 **本地原型补丁包**,不改动对面仓库。合入时按以下步骤执行。 ## 1. 文件落点 | 本目录文件 | 落到对面仓库 | 说明 | |---|---|---| | `native/ActuateProtocol.cs` | `code/backend/services/windows-runner/native/` | 新增类型,不动现有文件 | | `native/ActuateService.cs` | 同上 | 新增类型,不动现有文件 | | `native/NativeInput.cs` | 同上 | 新增类型;**NativeWindows.cs 保持只读边界原样不动**(对面注释明言该层无输入接口,编译边界得以保全) | csproj 无需改动:`UseWindowsForms` 已启用(SendKeys/Clipboard/Screen 可用),FlaUI 引用与本补丁无关。 ## 2. Program.cs 分发改造(最小 diff) 现有 Main 只认 `observe`。改造为按 Operation 分发: ```csharp // 原:if (request.ProtocolVersion != 1 || request.Operation != "observe" || ...) // 改为先解公共头再分发(保持 64KiB/UTF-8/UUID 校验全部不变): if (request.ProtocolVersion != 1 || !Guid.TryParse(requestId, out _)) throw new RunnerException("request_invalid"); switch (request.Operation) { case "observe": { /* 原 observe 路径逐字保留 */ } case "actuate": { var actuate = JsonSerializer.Deserialize(line, JsonOptions) ?? throw new RunnerException("request_invalid"); var result = ActuateService.Actuate(actuate, evidenceRoot); Write(new RunnerResponse(1, requestId, true, null, null, result)); return 0; } default: throw new RunnerException("request_invalid"); } ``` RunnerResponse 需加一个可选字段(observe 的线上形状不变,`WhenWritingNull` 已保证): ```csharp internal sealed record RunnerResponse(int ProtocolVersion, string RequestId, bool Ok, ComputerObservation? Data, string? ErrorCode, ActuateResult? ActuateData = null); ``` ## 3. TS 宿主侧(native-observer.ts → actuate 适配器,属后续工作) - 请求侧:actuate 前先用 observe 解析**精确**窗口标题与进程名(RPA 侧"标题包含匹配"的语义在宿主层弥合,见 PORTING-MAP 第二节); - 响应侧:对 ActuateData.Evidence 逐项复核(realpath 归属证据根目录、SHA-256、PNG 魔数——复用 native-observer.ts 现成校验); - 高影响审批:`rpa.windows` manifest 已是 `type=highImpact + idempotencyRequired`,capability-gateway 的精确 actionId 放行与租约语义直接适用,**无需改 gateway**; - 超时:宿主杀进程超时须覆盖 mouse_move_smooth 最坏路径(≈4s)+ 两次截图(≈2s),建议 ≥15s。 ## 4. 验证计划(用对面现成夹具) - `test/fixture/`(AgentWorker.WindowsFixture):仿照 `native-observer.integration.test.ts` 增加 actuate 集成测试: 1. `mouse_move` → GetCursorPos 断言落点; 2. `mouse_click`(无桌面断言,仅断言 ok + 证据 SHA 与文件存在); 3. `keyboard_send`(chars 模式)→ UIA 读回文本; 4. `window_activate` → GetForegroundWindow 断言; 5. 反向用例:越权窗口 → `window_not_allowed`;Session 不匹配 → `desktop_session_mismatch`;非法 Kind → `action_invalid`。 - 兼容性矩阵(微信/Office/UWP)按对面既有测试纪律单独排期,不在本补丁范围。 ## 5. 门禁合规清单(对面 2026-11-06 新规,硬性) 合入提交必须**同一提交内**包含: 1. `项目资料/开发方案/当前事实.json`:factRevision 递增,trackedInputs 如受影响同步更新; 2. 相关 README 的机器生成区块(`node code/tooling/document-facts.mjs --write` 重新生成); 3. `code/backend/README.md` Windows 行状态更新("click/type" 从"或随本轮交付"改为已实现口径); 4. 提交前本地跑:`node code/tooling/document-facts.mjs --check` && `node code/tooling/ci-gate.mjs --mode fast` && `git diff --check`。 ## 6. 验证状态与已知限制 - **语法级人工审查已完成;未编译**——本机无 .NET SDK,编译验证需在装 .NET 10 SDK 后进行(`dotnet build` native/csproj),或由对面侧首次合入时跑; - `Clipboard.SetDataObject` 依赖 STA:对面 Main 已是 `[STAThread]`,满足; - mouse_event → SendInput 为现代等价替换(标志位映射见 PORTING-MAP 第四节);如对面评审倾向零差异,可回退为 mouse_event P/Invoke,一行替换; - 平滑移动公式中的伪随机抖动与 JS `%` 保号语义一致(C# `%` 对 double 同为保号),无漂移。