refactor(picker): drop the Windows PowerShell fallback chain

The win32 tier is exactly the koffi IFileOpenDialog child process; any failure surfaces as-is. The pwsh -> Windows PowerShell 5.1 cascade, the shared WinForms script, and the triple-miss AggregateError are deleted: koffi is a packaged dependency whose availability the install guarantees, so no mechanism fallback exists (the browse backend remains the fallback at the composition level). The pwsh-first DPI picker-fix note is consolidated into a new simplification note recording the reversal.
This commit is contained in:
Huanqi Cao
2026-08-04 01:41:07 +08:00
parent 4201eaed3f
commit 4cb5f328bb
17 changed files with 143 additions and 225 deletions

View File

@@ -2,11 +2,10 @@
* Native backend of the directory-picker seam: registers `ctx.directoryPicker`
* with the `native` capability, opening one native OS chooser on the host
* display per pick (macOS `osascript`, Linux Zenity with a KDialog fallback;
* Windows opens the modern `IFileOpenDialog` in-process — a koffi-driven COM
* conversation on a worker thread — and falls back to a PowerShell-hosted
* dialog (`pwsh`, then Windows PowerShell 5.1) when that native surface is
* unavailable). Only viable when the operator sits at the host's screen;
* remote deployments compose the browse backend instead.
* Windows opens the modern `IFileOpenDialog` in a spawned child process — a
* koffi-driven COM conversation on the child's main thread). Only viable when
* the operator sits at the host's screen; remote deployments compose the
* browse backend instead.
* @module @deepseek-ai/dsh-host-directory-picker-native
*/

View File

@@ -67,62 +67,13 @@ export async function pickNativeDirectory(
}
if (platform === 'win32') {
// Primary: the in-process koffi-backed IFileOpenDialog worker — the modern
// picker with per-monitor-v2 DPI, no PowerShell dependency, and abort
// support. Any non-abort failure (koffi unavailable, ancient Windows, COM
// refusal) falls back to the PowerShell chain below.
// The koffi-backed IFileOpenDialog child process — the modern picker with
// per-monitor-v2 DPI and abort support. koffi is a packaged dependency
// whose availability the install guarantees, so there is no fallback
// tier: any failure surfaces as-is (the former PowerShell chain was
// removed — see the simplification Agent Note).
const pickDialog = internals.pickWin32Dialog ?? pickWin32Directory
let dialogError: unknown
try {
return await pickDialog(signal)
} catch (error: unknown) {
rethrowIfAborted(signal, error)
dialogError = error
}
// PowerShell fallback: PowerShell 7 renders the modern IFileDialog folder
// picker, while Windows PowerShell 5.1's FolderBrowserDialog is hardwired
// to the legacy SHBrowseForFolder tree. Prefer pwsh, but ANY pwsh failure
// falls back to 5.1 (which every Windows ships): a resolvable pwsh can
// still be unable to deliver the dialog — PowerShell 6 has no WinForms,
// so its Add-Type exits 1, not ENOENT. Both hosts spawn DPI-unaware, so
// the script opts the process into system DPI awareness before any window
// is created. No Description is set: the modern dialog renders it as a
// bottom strip and the classic dialog as an unthemed box.
const script = [
"$ErrorActionPreference = 'Stop'",
"Add-Type -TypeDefinition 'using System; using System.Runtime.InteropServices; public static class DpiAware { [DllImport(\"user32.dll\")] public static extern bool SetProcessDPIAware(); }'",
'[DpiAware]::SetProcessDPIAware() | Out-Null',
'Add-Type -AssemblyName System.Windows.Forms',
'$dialog = New-Object System.Windows.Forms.FolderBrowserDialog',
'$dialog.ShowNewFolderButton = $true',
'$result = $dialog.ShowDialog()',
'if ($result -eq [System.Windows.Forms.DialogResult]::OK) {',
' [Console]::OutputEncoding = [System.Text.Encoding]::UTF8',
' [Console]::WriteLine($dialog.SelectedPath)',
'}',
].join('; ')
let pwshError: unknown
try {
const result = await run('pwsh.exe', ['-NoProfile', '-STA', '-Command', script], signal)
return outputPath(result.stdout)
} catch (error: unknown) {
rethrowIfAborted(signal, error)
pwshError = error
}
try {
const result = await run('powershell.exe', ['-NoProfile', '-STA', '-Command', script], signal)
return outputPath(result.stdout)
} catch (error: unknown) {
rethrowIfAborted(signal, error)
// Triple miss: every tier failed. Surface all three causes — the
// in-process dialog's reason is otherwise unrecoverable from the last
// PowerShell error alone.
throw new AggregateError(
[dialogError, pwshError, error],
'native directory picker failed: the in-process dialog and both PowerShell hosts failed',
)
}
return await pickDialog(signal)
}
if (platform === 'linux') {