From 624d13f2ef4cb94ed8217025b6c5df7e018ae0c8 Mon Sep 17 00:00:00 2001 From: yu <191972707@qq.com> Date: Thu, 5 Nov 2026 13:22:33 +0800 Subject: [PATCH 1/4] fix: StrUtil_Replace enum mapping - search select value was treated as literal text Catalog contract: search is a select ("0"=content / "1"=digits / "2"=phone / "3"=email / "4"=ID card / "5"=custom regex) with the target in Config. Old implementation used first(args, ["search","Config"]) so the enum value "0" was split on as literal text, making content-replace never work (same family as W26-W33 enum mapping bugs). Now implements all six modes with preset patterns, regex mode guarded by try/catch, and falls back to legacy literal behavior when Config is empty. --- packages/rpa-core/src/capabilities/data.ts | 24 ++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/packages/rpa-core/src/capabilities/data.ts b/packages/rpa-core/src/capabilities/data.ts index 50e042a..9d7c423 100644 --- a/packages/rpa-core/src/capabilities/data.ts +++ b/packages/rpa-core/src/capabilities/data.ts @@ -111,9 +111,29 @@ register("StrUtil_InStr", (_ctx, _node, args) => { }); register("StrUtil_Replace", (_ctx, _node, args) => { const text = str(first(args, ["string"])); - const search = str(first(args, ["search", "Config"])); + // catalog 契约:search 为下拉枚举("0"内容/"1"数字/"2"手机号/"3"邮箱/"4"身份证/"5"自定义正则),查找目标在 Config + // 旧实现直接把枚举值 "0" 当字面文本分割,导致按内容替换永远不生效(W26 系枚举映射 bug 同族) + const modeRaw = first(args, ["search"]); + const mode = str(modeRaw === undefined ? "0" : modeRaw); + const config = str(first(args, ["Config"])); const replacement = str(first(args, ["replace"])); - return text.split(search).join(replacement); + const presets: Record = { + "1": /\d+/g, + "2": /1[3-9]\d{9}/g, + "3": /[\w.+-]+@[\w-]+(?:\.[\w-]+)+/g, + "4": /\d{17}[\dXx]/g + }; + if (presets[mode]) return text.replace(presets[mode], replacement); + if (mode === "5") { + try { + return text.replace(new RegExp(config, "g"), replacement); + } catch { + return text; + } + } + // "0" = 指定内容;兼容旧契约:Config 为空时退回把 search 当字面文本 + const target = config || mode; + return text.split(target).join(replacement); }); register(["StrUtil_LowerCase"], (_ctx, _node, args) => str(first(args, ["string"])).toLowerCase()); register(["StrUtil_UpperCase"], (_ctx, _node, args) => str(first(args, ["string"])).toUpperCase()); From 0db3e59f75051fb45334d699bf12d6a60c3bb084 Mon Sep 17 00:00:00 2001 From: yu <191972707@qq.com> Date: Thu, 5 Nov 2026 13:40:29 +0800 Subject: [PATCH 2/4] fix: File_DecompressFile misleading "ADM-ZIP: Invalid filename" for missing archive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit adm-zip throws "ADM-ZIP: Invalid filename" both for corrupt entries AND when the archive file simply does not exist, which sent us chasing phantom filename issues. Now the handler checks file existence first and reports "压缩包不存在:" with a recommendation pointing out that File_CompressFile appends .zip to the full source name (a.txt -> a.txt.zip) and that the compress node's output variable should be used to chain the real artifact path. --- packages/rpa-core/src/capabilities/files.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/rpa-core/src/capabilities/files.ts b/packages/rpa-core/src/capabilities/files.ts index aeb7f94..f594bd2 100644 --- a/packages/rpa-core/src/capabilities/files.ts +++ b/packages/rpa-core/src/capabilities/files.ts @@ -246,6 +246,10 @@ register("File_CompressFile", async (_ctx, node, args) => { register("File_DecompressFile", async (_ctx, node, args) => { const source = requirePath(pick(args, ["folder_path", "path", "zip_path"]), "压缩包路径", node.id, node.componentId); + // adm-zip 对不存在的文件抛 "ADM-ZIP: Invalid filename",极具误导性;先显式检查文件存在性 + await fs.access(source).catch(() => { + throw new RuntimeError("file_not_found", `压缩包不存在:${source}`, { nodeId: node.id, componentId: node.componentId, recommendation: "确认压缩包实际产物路径(压缩组件默认在原文件名后追加 .zip,如 a.txt 会压成 a.txt.zip),或用压缩节点的输出变量传入" }); + }); const saveToSourceDir = String(args.direct_type ?? "1") !== "0"; const target = saveToSourceDir ? path.dirname(source) From 124f11bd6bd9425f57a8549782fe10c7760573e9 Mon Sep 17 00:00:00 2001 From: yu <191972707@qq.com> Date: Thu, 5 Nov 2026 13:43:44 +0800 Subject: [PATCH 3/4] fix: Dialog_Notice/MsgBox MessageBox options arg - invalid HelpButton value Previous topmost attempt passed 'HelpButton' in the MessageBoxOptions position of MessageBox::Show, so PowerShell failed at runtime with "cannot convert value HelpButton to type System.Windows.Forms.MessageBoxOptions" (reproduced in flow run: notice node failed at char 196). Now passes the documented MB_TOPMOST flag (MessageBoxOptions)262144 as the 6th argument, keeping the six-argument overload (OK / Information / Button1 / topmost). --- packages/rpa-core/src/capabilities/desktop.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/rpa-core/src/capabilities/desktop.ts b/packages/rpa-core/src/capabilities/desktop.ts index 6e7a828..77097e5 100644 --- a/packages/rpa-core/src/capabilities/desktop.ts +++ b/packages/rpa-core/src/capabilities/desktop.ts @@ -154,7 +154,9 @@ const notice: CapabilityHandler = async (_ctx, _node, args) => { // MessageBox 阻塞等用户点确定,同样不能吃 powershell() 默认 30s 超时;且 MB_TOPMOST 保证弹窗不被主窗口盖住 const waitSeconds = number(args, ["wait_time", "waitTime", "timeout"], 600); const timeoutMs = Math.max(10000, Math.round(waitSeconds * 1000)); - await powershell(`Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show(${psString(content)},${psString(title)},'OK','Information','Button1','HelpButton',1) | Out-Null`, timeoutMs); + // MessageBoxOptions 第 6 参数用 MB_TOPMOST(0x40000=262144) 标志位实现置顶;此前误传 'HelpButton'(图标枚举值) + // 导致 PowerShell 类型转换失败「无法将 HelpButton 转换为 MessageBoxOptions」 + await powershell(`Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show(${psString(content)},${psString(title)},'OK','Information','Button1',[System.Windows.Forms.MessageBoxOptions]262144) | Out-Null`, timeoutMs); return true; }; From 9e12431db89874cc9b99e5256dbcd72c807f3621 Mon Sep 17 00:00:00 2001 From: yu <191972707@qq.com> Date: Thu, 5 Nov 2026 13:48:49 +0800 Subject: [PATCH 4/4] fix: notice dialog topmost - owner form pattern instead of invalid enum value 262144 (Win32 MB_TOPMOST) has no mapping in the .NET MessageBoxOptions enum, whose only members are RightAlign/RtlReading/ServiceNotification etc., so PowerShell rejected it: "cannot convert 262144 to MessageBoxOptions" (second failure of the topmost attempt). Correct documented way is passing a TopMost invisible owner form as the first argument of MessageBox.Show. Wrapped in try/catch that falls back to a plain Show, so the dialog can no longer fail the node even if the owner pattern misbehaves on some machine. --- packages/rpa-core/src/capabilities/desktop.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/rpa-core/src/capabilities/desktop.ts b/packages/rpa-core/src/capabilities/desktop.ts index 77097e5..c72af81 100644 --- a/packages/rpa-core/src/capabilities/desktop.ts +++ b/packages/rpa-core/src/capabilities/desktop.ts @@ -154,9 +154,10 @@ const notice: CapabilityHandler = async (_ctx, _node, args) => { // MessageBox 阻塞等用户点确定,同样不能吃 powershell() 默认 30s 超时;且 MB_TOPMOST 保证弹窗不被主窗口盖住 const waitSeconds = number(args, ["wait_time", "waitTime", "timeout"], 600); const timeoutMs = Math.max(10000, Math.round(waitSeconds * 1000)); - // MessageBoxOptions 第 6 参数用 MB_TOPMOST(0x40000=262144) 标志位实现置顶;此前误传 'HelpButton'(图标枚举值) - // 导致 PowerShell 类型转换失败「无法将 HelpButton 转换为 MessageBoxOptions」 - await powershell(`Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.MessageBox]::Show(${psString(content)},${psString(title)},'OK','Information','Button1',[System.Windows.Forms.MessageBoxOptions]262144) | Out-Null`, timeoutMs); + // 置顶方案: MB_TOPMOST/262144 是 Win32 老接口标志, .NET MessageBoxOptions 枚举里没有(只有 RightAlign/RtlReading/ServiceNotification 等), + // 传 262144 会被 PS 枚举转换拒绝。正确做法是传一个 TopMost 的隐形属主窗体; + // 属主窗体万一失败则 catch 降级为普通 Show, 保证弹窗一定出现不阻塞流程。 + await powershell(`Add-Type -AssemblyName System.Windows.Forms; $f = New-Object System.Windows.Forms.Form; $f.TopMost = $true; try { [System.Windows.Forms.MessageBox]::Show($f,${psString(content)},${psString(title)},'OK','Information') | Out-Null } catch { [System.Windows.Forms.MessageBox]::Show(${psString(content)},${psString(title)},'OK','Information') | Out-Null }; $f.Dispose()`, timeoutMs); return true; };