提交前确认 / Pre-submit checklist
运行环境 / Environment
DSH Web(浏览器访问)/ DSH Web (browser access)
DSH Web 版本 + 浏览器 / DSH Web version + Browser
0.1.1-rc.2
Desktop 实现仓库 / Desktop repo(源头 / upstream)
https://github.com/dsh-tauri-desk/deepseek-harness-desktop
插件版本 / Plugin version
0.18.1-alpha.0
类别 / Category
🐛 Bug(功能异常 / Something is broken)
描述 / Description
在 Windows 上,右键文件 → 「打开方式 → 资源管理器」(open.external 的 reveal 动作)在文件路径含空格、括号、&、# 等字符时,资源管理器会打开错误的目录且不选中目标文件。根因是 spawn('explorer.exe', ['/select,' + path]) 在路径带空格时会被 Node 把整个 /select, token 包上引号,Windows Explorer 的 /select, 无法解析这种形式。修复方式是只给路径部分加引号(/select,"")并对 Windows reveal 设置 。
#根因诊断
src/open-external.ts构建 Windows reveal 命令如下:
case 'win32':
return { command: 'explorer.exe', args: [`/select,${path}`] }
且 launchExternal 使用 Node 默认选项进行 spawn:
const child = spawn(spec.command, spec.args, { detached: true, stdio: 'ignore' })
在 Windows 上,只要路径包含空格,Node 默认的参数序列化会将整个 /select,<path> 用双引号包裹。Explorer 随后接收到:
explorer.exe "/select,C:\Users\me\test folder (x) & y\file #x.txt"
Windows Explorer 的 /select, 开关无法解析这种形式;它会打开无关的文件夹 / 不会高亮目标文件。当路径没有空格时,不会被重新加引号,因此可以正常工作。
测试复现
右键点击侧边栏中的文件 → 打开方式 → 资源管理器。
情况 A(正常):路径 D:\project\file.txt → Explorer 打开文件夹并选中 file.txt。
情况 B(失败):路径 D:\project\test folder (x) & y\file #x.txt → Explorer 打开错误的目录 / 不选中该文件。
此问题已通过 UI 和直接调用完全相同的 spawn('explorer.exe', ['/select,' + path], { detached: true, stdio: 'ignore' }) 调用复现。
预期行为
无论路径中包含空格 / ( ) / & / #,Explorer 都应打开所在文件夹并选中文件。
修复
将 win32 分支改为仅对路径部分加引号:
case 'win32':
return { command: 'explorer.exe', args: [`/select,"${path}"`] }
并通过启用 windowsVerbatimArguments 来保留 Windows reveal 的 spawn 引号:
const spawnOptions: import('node:child_process').SpawnOptions = { detached: true, stdio: 'ignore' }
if (platform === 'win32' && action === 'reveal') spawnOptions.windowsVerbatimArguments = true
const child = spawn(spec.command, spec.args, spawnOptions)
应用此更改后,相同的特殊字符路径(D:...\test folder (x) & y\file #x.txt)能正确打开并选中文件。
该修复保持无 shell 环境(不使用 cmd/PowerShell),遵循现有设计原则,即不让 shell 重新解释路径字符。
其他背景
0.17.1 有一个相关但不同的 bug:参数被拆分为两个条目 ['/select,', path]。这个问题在 0.18 中通过拼接为 /select,<path> 修复,但拼接后的形式对于包含空格/特殊字符的路径仍然失败,因为 Node 会重新引用整个 路径。
补充信息 / Additional context
OS: Windows (10/11)
Host: DeepSeek Harness Desktop (Tauri2), web profile
Plugin: dsh-better-sidebar 0.18.1-alpha.0 (github:omdsh-dev/DSH-better-sidebar)
The host half runs under Node (child_process.spawn)
提交前确认 / Pre-submit checklist
运行环境 / Environment
DSH Web(浏览器访问)/ DSH Web (browser access)
DSH Web 版本 + 浏览器 / DSH Web version + Browser
0.1.1-rc.2
Desktop 实现仓库 / Desktop repo(源头 / upstream)
https://github.com/dsh-tauri-desk/deepseek-harness-desktop
插件版本 / Plugin version
0.18.1-alpha.0
类别 / Category
🐛 Bug(功能异常 / Something is broken)
描述 / Description
在 Windows 上,右键文件 → 「打开方式 → 资源管理器」(open.external 的 reveal 动作)在文件路径含空格、括号、&、# 等字符时,资源管理器会打开错误的目录且不选中目标文件。根因是 spawn('explorer.exe', ['/select,' + path]) 在路径带空格时会被 Node 把整个 /select, token 包上引号,Windows Explorer 的 /select, 无法解析这种形式。修复方式是只给路径部分加引号(/select,"")并对 Windows reveal 设置 。
#根因诊断
src/open-external.ts构建 Windows reveal 命令如下:且 launchExternal 使用 Node 默认选项进行 spawn:
在 Windows 上,只要路径包含空格,Node 默认的参数序列化会将整个
/select,<path>用双引号包裹。Explorer 随后接收到:Windows Explorer 的
/select, 开关无法解析这种形式;它会打开无关的文件夹 / 不会高亮目标文件。当路径没有空格时,不会被重新加引号,因此可以正常工作。测试复现
右键点击侧边栏中的文件 → 打开方式 → 资源管理器。
情况 A(正常):路径 D:\project\file.txt → Explorer 打开文件夹并选中 file.txt。
情况 B(失败):路径 D:\project\test folder (x) & y\file #x.txt → Explorer 打开错误的目录 / 不选中该文件。
此问题已通过 UI 和直接调用完全相同的 spawn('explorer.exe', ['/select,' + path], { detached: true, stdio: 'ignore' }) 调用复现。
预期行为
无论路径中包含空格 / ( ) / & / #,Explorer 都应打开所在文件夹并选中文件。
修复
将 win32 分支改为仅对路径部分加引号:
并通过启用 windowsVerbatimArguments 来保留 Windows reveal 的 spawn 引号:
应用此更改后,相同的特殊字符路径(D:...\test folder (x) & y\file #x.txt)能正确打开并选中文件。
该修复保持无 shell 环境(不使用 cmd/PowerShell),遵循现有设计原则,即不让 shell 重新解释路径字符。
其他背景
0.17.1 有一个相关但不同的 bug:参数被拆分为两个条目
['/select,', path]。这个问题在 0.18 中通过拼接为/select,<path>修复,但拼接后的形式对于包含空格/特殊字符的路径仍然失败,因为 Node 会重新引用整个 路径。补充信息 / Additional context
OS: Windows (10/11)
Host: DeepSeek Harness Desktop (Tauri2), web profile
Plugin: dsh-better-sidebar 0.18.1-alpha.0 (github:omdsh-dev/DSH-better-sidebar)
The host half runs under Node (child_process.spawn)