From f390db9c94bca46259f19417fb21bf435699f0f6 Mon Sep 17 00:00:00 2001 From: yu <191972707@qq.com> Date: Tue, 3 Nov 2026 17:43:46 +0800 Subject: [PATCH] =?UTF-8?q?=E9=94=99=E8=AF=AF=E8=BE=93=E5=87=BA=E9=80=9A?= =?UTF-8?q?=E9=81=93=E6=94=B9=E9=80=A0:=20GBK=20=E5=9B=9E=E9=80=80?= =?UTF-8?q?=E8=A7=A3=E7=A0=81=E4=B8=8E=20RPA=5FERROR=20=E6=A0=87=E8=AE=B0?= =?UTF-8?q?=E9=98=B2=E6=88=AA=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/capabilities/desktop.ts | 52 ++++++++++++++++++++++++-------- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/src/main/capabilities/desktop.ts b/src/main/capabilities/desktop.ts index b6d9d55..4952ead 100644 --- a/src/main/capabilities/desktop.ts +++ b/src/main/capabilities/desktop.ts @@ -82,10 +82,26 @@ function encoded(command: string): string { function decodeCliXml(detail: string): string { const lines = [...detail.matchAll(/(.*?)<\/S>/gs)].map((m) => m[1]); if (lines.length === 0) return detail; - // 只留「+ 类别/错误消息」首行, 去掉位置堆栈噪声; _xHHHH_ 是 CLIXML 转义 + // _xHHHH_ 是 CLIXML 转义 const decoded = lines.join("\n").replace(/_x([0-9A-Fa-f]{4})_/g, (_, h) => String.fromCharCode(parseInt(h, 16))); - const first = decoded.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("所在位置") && !l.startsWith("+") && !l.startsWith("~~") && !l.includes("CategoryInfo") && !l.includes("FullyQualifiedErrorId") && !l.startsWith(" l.trim()); + const position = parts.filter((l) => l.startsWith("所在位置")).slice(0, 1); + const message = parts.filter( + (l) => l && !l.startsWith("所在位置") && !l.startsWith("+") && !l.startsWith("~~") && !l.includes("CategoryInfo") && !l.includes("FullyQualifiedErrorId") && !l.startsWith(" { @@ -95,12 +111,22 @@ export async function powershell(command: string, timeout = 30000): Promise 0 ? decodePipe(stderrBuf) : err.message; + const detail = decodeCliXml(stderrText); throw new RuntimeError("desktop_command_failed", `桌面操作失败:${detail}`, { detail }); } } @@ -179,11 +205,13 @@ const mouseRoll: CapabilityHandler = async (_ctx, node, args) => { }; /** 把匹配标题的窗口带到前台;多招式依次兜底(应用点击「运行」后自身是前台, - * Windows 前台锁定策略会拒绝普通 SetForegroundWindow,最小化再恢复可绕过) */ + * Windows 前台锁定策略会拒绝普通 SetForegroundWindow,最小化再恢复可绕过)。 + * 失败用 RPA_ERROR 标记写 stdout 后 exit 1:不走 throw/CLIXML,避免控制台宽度截断和编码歧义 */ function activateWindowScript(selector: string): string { const safeTitle = selector.replace(/'/g, "''"); - return `$p = Get-Process | Where-Object { $_.MainWindowTitle -and $_.MainWindowTitle -like ${psString(`*${selector}*`)} } | Select-Object -First 1 -if (-not $p) { $vis = (Get-Process | Where-Object { $_.MainWindowTitle } | Select-Object -First 12 | ForEach-Object { '『' + $_.MainWindowTitle + '』' }) -join ' '; throw ('找不到窗口:${safeTitle}。当前可见窗口标题: ' + $vis) } + return `function RpaFail($m) { [Console]::Out.WriteLine('RPA_ERROR::' + $m); exit 1 } +$p = Get-Process | Where-Object { $_.MainWindowTitle -and $_.MainWindowTitle -like ${psString(`*${selector}*`)} } | Select-Object -First 1 +if (-not $p) { $vis = (Get-Process | Where-Object { $_.MainWindowTitle } | Select-Object -First 12 | ForEach-Object { '『' + $_.MainWindowTitle + '』' }) -join ' '; RpaFail ('找不到窗口:${safeTitle}。当前可见窗口标题: ' + $vis) } Add-Type -MemberDefinition '[DllImport("user32.dll")] public static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] public static extern bool ShowWindow(IntPtr h, int cmd); [DllImport("user32.dll")] public static extern bool IsIconic(IntPtr h); [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr h); [DllImport("user32.dll")] public static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, UIntPtr dwExtraInfo); [DllImport("user32.dll")] public static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr pid); [DllImport("user32.dll")] public static extern bool AttachThreadInput(uint a, uint b, bool attach); [DllImport("user32.dll")] public static extern void SwitchToThisWindow(IntPtr hWnd, bool fAltTab); [DllImport("kernel32.dll")] public static extern uint GetCurrentThreadId();' -Name RpaFocus2 -Namespace WinRpaFocus2 | Out-Null $h = $p.MainWindowHandle if ([WinRpaFocus2.RpaFocus2]::IsIconic($h)) { [void][WinRpaFocus2.RpaFocus2]::ShowWindow($h, 9); Start-Sleep -Milliseconds 150 } @@ -210,7 +238,7 @@ if ([WinRpaFocus2.RpaFocus2]::GetForegroundWindow() -ne $h) { [void][WinRpaFocus2.RpaFocus2]::ShowWindow($h, 9) Start-Sleep -Milliseconds 300 } -if ([WinRpaFocus2.RpaFocus2]::GetForegroundWindow() -ne $h) { throw '无法把窗口带到前台,请确认窗口未被关闭或全屏占用' }`; +if ([WinRpaFocus2.RpaFocus2]::GetForegroundWindow() -ne $h) { RpaFail '无法把窗口带到前台,请确认窗口未被关闭或全屏占用' }`; } const setActiveWindow: CapabilityHandler = async (_ctx, node, args) => {