# A-route 离线打开真实 frmP2PChat 会话窗口 日期:2026-07-11 分支:`codex/a-route-open-real-chat-window` 目标:不登录、不连服务端,在本机离线样本中打开真实 `IMPP.Client.Business.ChatManager.SingleChat.frmP2PChat` 窗口,供后续 RPA 对真实窗口做 UIA 控制。 ## 结论 已经在未登录/离线环境下打开真实 `frmP2PChat`。 成功命令: ```powershell powershell -NoProfile -ExecutionPolicy Bypass -File scripts\open-offline-real-frmP2PChat.ps1 -RuntimeMode Deps -ShowWindow -HoldSeconds 1 ``` 成功产物: - 结果 JSON:`E:\coding\codex\isphere-ai-bridge\runs\offline-chat-window\real-frmP2PChat-result-20260711-205312.json` - HWND:`0xC0CB4` - 截图:`E:\coding\codex\isphere-ai-bridge\runs\offline-chat-window\real-frmP2PChat-20260711-205312.png` - UIA dump:`E:\coding\codex\isphere-ai-bridge\runs\offline-chat-window\uia-dump-real-frmP2PChat-20260711-205312.json` - UIA root: - `automation_id = frmP2PChat` - `control_type = Window` - `framework_id = WinForm` - `visible = true` UIA dump 已确认可见关键控件: - `rtbRecvMessage` - `rtbSendMessage` - `btnSend` 窗口内显示“您已处于离线状态,无法发送消息,请上线后再次尝试!”。这是预期状态;本轮目标不是发送消息,也不是上传文件,只是打开真实会话窗口。 ## 新增脚本 ### 1. 真实窗口打开脚本 `scripts/open-offline-real-frmP2PChat.ps1` 关键能力: - 自动切到 32-bit PowerShell,避免 x86 客户端程序集 `BadImageFormatException`。 - 只读加载真实客户端目录: `runs/offline-real-client-window/full/zyl/Impp` - 使用 `AssemblyResolve` 从真实客户端目录解析依赖。 - 构造真实类型: `IMPP.Client.Business.ChatManager.SingleChat.frmP2PChat` - 支持不 Show 的构造验证,也支持 `-ShowWindow` 真实显示、截图和 UIA dump。 - 捕获 WinForms `ThreadException`,输出 IL offset、堆栈和 JSON 证据,避免 .NET 异常弹窗卡死。 ### 2. 构造函数 IL / NullRisk 提取脚本 `scripts/extract-frmP2PChat-il-risk.ps1` 输出: - 完整构造函数 IL: `runs/offline-chat-window/frmP2PChat-ctor.il.txt` - `callvirt` / `ldfld` NullRisk CSV: `runs/offline-chat-window/frmP2PChat-ctor-null-risk.csv` - 风险摘要: `runs/offline-chat-window/frmP2PChat-ctor-null-risk.md` 提取结果: - 构造函数源 IL 行:`406271-406860` - 构造函数行数:`590` - `callvirt` / `ldfld` 风险项:`150` ## 关键 IL 证据 构造函数签名: ```text frmP2PChat(com.vision.smack.Chat chat, string pluginInfo, string extendTabJson) ``` 高信号 NullRisk: | IL | 证据 | 需要补的运行态 | |---|---|---| | `IL_00f6` | `XMPPConnection::add_OnConnectError` | `IMPPManager.Instance.Connection` / backing field | | `IL_0111` | `XMPPConnection::add_OnReConnectOk` | 同上 | | `IL_013b` / `IL_0140` | `P2PChat::get_OtherJid()` / `Jid::getBareJid()` | `P2PChat.OtherJid` | | `IL_0156` | `IMPPManager::get_UserInfo()` | `IMPPManager._userInfo` | | `IL_0575` | `MessageCenter::add_OnTcpMessageArrived` | `IMPPManager.MessageCenter` | | `IL_0614` | `RosterManager::UpdateStrangerStatus` | `IMPPManager.RosterManager` | | `IL_0631` | `BaseConfig::VisualPhoneEnable()` | `Config.BaseConfig` | Show 阶段新增证据: | IL | 证据 | 处理 | |---|---|---| | `frmP2PChat_Load IL_004d/0058` | `Config::get_BaseConfig()` 后 `BaseConfig::RemoteControlVisible()` | 补 `Config.k__BackingField` | | `frmChatBase_Activated IL_0000/0005` | `IMPPManager.get_Instance().get_frmMain().BringModalFormToFront()` | 补 `IMPPManager.k__BackingField` | | `frmP2PChat_Load IL_137f` | `UserInfoManager.getUserInfo(otherBareJid,true)` 后使用 `UserInfo::get_CompanyID()` | 构造完成后预热 `UserInfoCache` | 注意:`UserInfoCache` 不能在 `frmP2PChat` 构造前预热。过早预热会让构造函数进入 `UCUserInformation.LoadUserInfo(UserInfo)`,在 `UCUserInformation::LoadUserInfo IL_0085` 触发新的 NRE。当前脚本是在真实窗体构造完成后、`Show()` 前预热缓存。 ## 最小 fake runtime 清单 当前成功路径最小补齐如下: 1. `IMPPManager.Instance` - `k__BackingField` - `k__BackingField` - `LogonUser` - `_userInfo` - `_rosterUserInfoList` 2. `SmarkManager` - `k__BackingField` 3. `XMPPConnection` - `Jid` - `UserName` - `LoginState` - `CompanyID` - `Resource` - `_packetListeners` - `_packetCollectors` 4. `IMPPManager` inherited manager fields/properties - `Connection` and `k__BackingField` - `IsConnected` and `k__BackingField` - `PresenceManager` and backing field - `P2PChatManager` and backing field - `RosterManager` and backing field 5. Config - `ConfigSystemManager.config` - `Config.k__BackingField` - `Config.k__BackingField` - `Config.k__BackingField` 6. `UserInfoCache` - 在 `frmP2PChat` 构造后、`Show()` 前加入自己和对端的 JID/ID 映射。 7. Chat 对象 - 真实 `com.vision.smack.P2PChat` - `OtherJid = codex-probe@offline.local` ## 验证命令 ```powershell powershell -NoProfile -ExecutionPolicy Bypass -File scripts\test-extract-frmP2PChat-il-risk.ps1 powershell -NoProfile -ExecutionPolicy Bypass -File scripts\extract-frmP2PChat-il-risk.ps1 -OutDir runs/offline-chat-window powershell -NoProfile -ExecutionPolicy Bypass -File scripts\test-open-offline-real-frmP2PChat-contract.ps1 powershell -NoProfile -ExecutionPolicy Bypass -File scripts\open-offline-real-frmP2PChat.ps1 -RuntimeMode Deps powershell -NoProfile -ExecutionPolicy Bypass -File scripts\open-offline-real-frmP2PChat.ps1 -RuntimeMode Deps -ShowWindow -HoldSeconds 1 ``` ## 当前边界 - 已成功打开真实 `frmP2PChat`。 - UIA 可 dump 到真实 `frmP2PChat`、`rtbRecvMessage`、`rtbSendMessage`、`btnSend`。 - 离线环境下窗口会提示无法发送消息,这是客户端真实离线状态表现,不影响后续 RPA 窗口定位。 - 本轮没有处理真实发送、真实上传、真实登录态。 - `btnSendFile` 在这次真实离线 UIA dump 中没有以 `btnSendFile` automation id 暴露;后续如果 RPA 需要上传文件,需要另开节点定位真实工具栏按钮或菜单路径。 ## 2026-07-12 主分支状态校准 该能力已经随 A-route 分支合并到 `main`。业务含义如下: - 已完成:真实离线 `frmP2PChat` 打开、UIA 识别、发送框定位、发送按钮定位。 - 已演示:在真实离线窗口内写入文本并点击 `btnSend`,可用于验证数字员工的窗口识别、定位和点击链路。 - 未完成:在线登录态下的真实发送、服务端 ack、接收方送达、发送记录匹配。 - 未完成:真实文件上传/发送。当前真实 UIA dump 没有稳定暴露 `btnSendFile`,文件发送要单独做工具栏/菜单定位。 因此,文档里出现的 helper `sent_message=true` 只能解释为“UI 发送动作已触发”,不能解释为“业务消息已送达”。 ## 下一轮建议 1. 把 A-route RPA 的窗口定位目标从 synthetic probe 切到真实 `frmP2PChat`: - root `automation_id=frmP2PChat` - send box `automation_id=rtbSendMessage` - send button `automation_id=btnSend` 2. 把真实窗口 HWND 接入 `isphere_send_message` 的 `uia_rpa` connector,形成可重复的本地 UI 动作 smoke。 3. 在线登录环境中补一次业务发送验证:发送前后记录、content hash、ack/发送记录、idempotency 不重复发送。 4. 如果后续需要文件按钮,再专门分析真实工具栏图标按钮、菜单项和 `ToolStrip` 命中路径。