[Bug] 0.1.7-rc.2 / master:Windows 上「打开目录 / 在资源管理器中显示」的窗口不可见、或只在任务栏不弹前台 —— SW_HIDE 泄漏 + Windows 前台锁 #8043
Replies: 3 comments
|
Confirming on Desktop 0.1.7-rc.2 (Windows 11, build 26100), with a minimal reproduction and a verified local workaround. Reproduction (100% deterministic)const { execFile } = require('node:child_process')
// hidden: no window ever appears; explorer.exe exits (code 1) after delegating
execFile('explorer.exe', ['file:///C:/Users/<user>/Documents'], { windowsHide: true }, () => {})
// visible: the folder window appears immediately
execFile('explorer.exe', ['file:///C:/Users/<user>/Documents'], { windowsHide: false }, () => {})Observed on the actual button click
Root cause
Suggested fixHide only console-only helpers, not GUI launchers — e.g. in windowsHide: command !== 'explorer.exe'Keep Local workaround applied (verified working)In-place patch of the packaged asar ( |
|
补充一份桌面版自带运行时上的实测证据,支持①(SW_HIDE 泄漏),并补一条本贴还没提到的限制。 1. 缺陷就在 Desktop 0.1.7-rc.2 的 asar 里(不只是 npm 的
|
| 宿主 | dsh-native-command 来源 |
点「在文件资源管理器中显示」 |
|---|---|---|
| web profile(3080) | npm 全局 0.1.5-rc.2 + 上述本地补丁 | 正常弹窗并选中目标项 ✅ |
| desktop profile | 自带 asar 内 0.1.7-rc.2(未打补丁) | 静默无反应 ❌(RPC 仍返回成功) |
与 #7982 的 A/B 结论一致:区分变量是 windowsHide(以及路径形态),不是退出码。
一点边界说明:本机没能观察到你说的第②条前台锁那一半 —— 补丁版在 0.1.5 web 宿主上窗口是正常提到前台的。所以前台锁可能只在「宿主进程长期处于后台」的桌面宿主场景才暴露(desktop 宿主与 web 宿主的前台状态不同),这边没有反证,只能认为 desktop 下表现更差,建议仍按你的修法做 best-effort 前台提升。
English summary: the exact defects also ship inside DSH Desktop 0.1.7-rc.2's bundled resources/app.asar — dsh/node_modules/@deepseek-ai/dsh-native-command/lib/index.js (size 40419) still has windowsHide: true and the file:// argv form, and has no revealExplorerPath. A local-patch workaround that fixes the npm/web profile on the same machine (verified via Shell.Application FocusedItem, including a CJK path) is not applicable to Desktop: the exe carries an INTEGRITY/ELECTRONASAR resource, i.e. asar integrity validation, so patching app.asar risks making Harness fail to start. Same-machine A/B: patched web tree reveals correctly; unpatched Desktop copy is a silent no-op.
|
Thanks for the added evidence — the offsets and size match our install exactly, so it is the same defect in both copies. One empirical datapoint that softens the "no local workaround on Desktop" conclusion for this shipped build: on our machine the official So on this build the integrity metadata is present but validation is not enforced (or not blocking); enforcement presumably depends on the |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
[Bug] 0.1.7-rc.2 / master:Windows 上「打开目录 / 在资源管理器中显示」的窗口不可见、或只在任务栏不弹前台
TL;DR
@deepseek-ai/dsh-native-command@0.1.7-rc.2(以及 master)在 Windows 上通过openNativePath()打开目录时有两个独立缺陷:runNativeCommand恒以windowsHide: true(STARTF_USESHOWWINDOW+SW_HIDE)启动子进程;Explorer 会把调用方的显示状态带给它创建或激活的窗口,于是窗口IsWindowVisible = false——而POST /open-in-app/open依然返回200 {ok:true},界面无任何报错。修复:① 目录改用
explorer.exe <encoded file URI>+windowsHide: false启动;② 在宿主进程内用koffi直接调user32(EnumWindows定位窗口 → 最小化 → 还原 →SetForegroundWindow),与打开并行、best-effort。版本与环境
@deepseek-ai/dsh-native-command0.1.7-rc.2(dist-tags.next),并对照masterexplorer目录 id,win32 上唯一的shell-open条目),以及revealNativePath()的「在资源管理器中显示」一、发布产物核验(0.1.7-rc.2 真实 npm tarball)
解开发布包逐处确认(
lib/index.js是 exports 映射实际加载的 bundle,lib/types/*.js是随包发布的同伴文件):即:Explorer 重写已发布(
Invoke-Item在全包内已无命中),但 runner 仍硬编码windowsHide: true,runExplorer也未覆盖它。二、缺陷一:窗口被创建但不可见(SW_HIDE 泄漏)
windowsHide: true在子进程的STARTUPINFO中设置SW_HIDE;当该子进程请求外壳打开/激活文件夹时,Explorer 会把调用方的显示状态应用到它创建(或激活)的窗口上——窗口不可见,而请求本身"成功"。实测(Windows 11 25H2 build 26200.9457;每次先关闭目标窗口,再启动一次,用
Shell.Application.Windows()+IsWindowVisible/IsIconic读取状态):windowsHideexplorer.exe <encoded file URI>IsWindowVisible=false、IsWindow=true、IsIconic=falseexplorer.exe <encoded file URI>rundll32 url.dll,FileProtocolHandler <path>被隐藏的窗口并未销毁:
ShowWindow(hwnd, SW_SHOW)可以恢复(实测有效)。宿主机上曾累计出现 8 个这样的隐形file:///D:/Innovation窗口。三、缺陷二:窗口只出现在任务栏、不弹到前台
修好可见性后,窗口会正常出现(
IsWindowVisible=true、IsIconic=false),但不进入前台:只多出一个任务栏按钮。根因两层,都在 Windows 侧:SetForegroundWindow返回False,系统只闪一下任务栏;实测(同上环境,浏览器为前台):
SetForegroundWindowAttachThreadInput+SetForegroundWindowSetWindowPos(HWND_TOP)(仅调 z 序,不抢焦点)HWND_TOPMOST→HWND_NOTOPMOSTSetForegroundWindow,执行进程随即退出SetForegroundWindow,由常驻进程执行结论:抬升必须由生命周期长于该次点击的进程执行——这正是把它放进 DSH 宿主进程(而非短命子进程)的原因。
边界:若另一个程序正在主动争抢前台(实测中,同机上一个交互式 TUI 控制台就会),任何启动器都无法稳压它。这属于系统级限制,不属于本缺陷。
四、修复
1)目录分支改用可见启动状态
packages/util/native-command/src/path-opener.ts→openWindowsPath(),目录交给 Explorer 自身的 CLI,并用可见启动状态;退出码 1 仍视为"已交接给运行中的外壳"(与revealNativePath()的既有处理一致):配套(缺陷一的根因所在,
src/runner.ts):把windowsHide提升为每次调用可选参数,默认true,所有既有调用方行为不变:(文件/文档路径保持原样,不改变"用默认程序打开"的语义。)
2)在宿主进程内抬升窗口
openWindowsPath()内,与打开并行发起一次 best-effort 抬升(失败不影响已经成功的打开):因为宿主进程常驻,不需要任何"保持激活"(见第三节最后两行实测)。
延迟:宿主内直调实测 窗口成为前台 555 ms、
openNativePath返回 662 ms;其中EnumWindows单次 2 ms、抬升动作约 230 ms(两次等待 140 + 90 ms),瓶颈回到explorer.exe自身创建窗口的耗时。实现注意:
koffi不允许重复定义同名类型。koffi.proto("bool EnumWindowsProc(...)")必须定义一次并缓存——若在轮询循环中每次重新定义,第二次迭代即抛Duplicate type name,而该异常会被 best-effort 的catch吞掉,表现为"补丁已生效、窗口却完全没有被抬升",非常难定位。五、影响面
openNativePath()打开目录的界面(最明显的是 open-in-app 的「文件资源管理器」条目),以及revealNativePath()的「在资源管理器中显示」。argv方式启动的条目(VS Code / Windows 终端 / Git Bash)不受影响。explorer.exe不触发文件关联、约 1 秒后仍返回 1,于是"报成功但什么都没打开"。English
Two independent defects when 0.1.7-rc.2 opens a folder on Windows.
(1) The window is created but invisible.
runNativeCommandalways spawns withwindowsHide: true, which setsSTARTF_USESHOWWINDOW+SW_HIDEin the child'sSTARTUPINFO; Explorer applies the caller's show state to the window it creates or re-activates, so the window isIsWindowVisible = falsewhilePOST /open-in-app/openstill answers200 {ok:true}and the UI shows no error. Verified against the published artifact:lib/types/runner.js:14(lib/index.js:22) hard-codeswindowsHide: true, andlib/types/path-opener.js:115(lib/index.js:143) callsawait run('explorer.exe', args, signal)with no launch-state option. Measured on Windows 11 25H2 (build 26200.9457):explorer.exe <encoded file URI>withwindowsHide: true→ invisible window; withwindowsHide: false→ visible.rundll32 url.dll,FileProtocolHandlerwithwindowsHide: trueis invisible too, so the flag — not the launcher — is the variable. Hidden windows are not destroyed (ShowWindow(hwnd, SW_SHOW)restores them; eight had accumulated on the reporting host).(2) The window only appears in the taskbar. With visibility fixed the window exists (
IsWindowVisible = true,IsIconic = false) but never reaches the foreground. Two Windows-side causes: a background process is refused by the foreground lock (SetForegroundWindowreturnsFalse, the shell only flashes the taskbar), and even the minimize → restore trick that does unlock the activation is undone the moment the activating process exits — measured: with an immediate exit the foreground is back on the browser within a second; when the raise runs from a long-lived process it stays. BareSetForegroundWindow,AttachThreadInput+SetForegroundWindow,SetWindowPos(HWND_TOP)and aHWND_TOPMOST→HWND_NOTOPMOSTcycle all failed to raise it (the last one even left the window minimized).Fix. (a)
openWindowsPath()opens a directory through Explorer's own CLI with a visible launch state —run('explorer.exe', args, signal, { windowsHide: false }), keeping exit code 1 as the delegated handoff — andsrc/runner.tsgains a per-callwindowsHideoption (defaulttrue, so every existing caller is unchanged). (b) The window is raised from the host process, in parallel with the open and best effort, using thekoffithat DSH already ships: binduser32.dll, find the window withEnumWindows(classCabinetWClass, title prefix"<folder name> - ", which is locale-independent),ShowWindow(SW_MINIMIZE)→ 140 ms →ShowWindow(SW_RESTORE)→ 90 ms →SetForegroundWindow. Because the host outlives the call, no keep-alive is needed. Measured: foreground after 555 ms,openNativePathreturns in 662 ms (EnumWindows2 ms, the raise ~230 ms), leaving Explorer's own window creation as the floor. Implementation trap:koffirefuses to define the same type name twice, sokoffi.proto(...)must be created once and cached — re-creating it inside the polling loop throwsDuplicate type namefrom the second iteration, and a best-effort catch turns that into "patched, yet the window is never raised".Scope. Windows only; affects every surface opening a directory through
openNativePath()(most visibly the open-in-app File Explorer entry) andrevealNativePath(). macOS/Linux branches andargv-launched entries (VS Code, Windows Terminal, Git Bash) are unaffected. Unrelated to the README's Known Limitation about non-interactive sessions, whereexplorer.exeopens nothing and still exits 1.All reactions