@sparkelf/dsh-patch-wsl-native-open 0.2.0-rc.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 DeepSeek
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "description": "Data-only exact official-source patch translating POSIX paths for the Windows interpreter on a WSL host",
3
+ "dshPatch": {
4
+ "formatVersion": 1,
5
+ "variants": [
6
+ {
7
+ "dsh": ">=0.1.6-alpha.1",
8
+ "file": "./patches/wsl-native-open.patch",
9
+ "id": "wsl-native-path-translation",
10
+ "target": {
11
+ "baseRevision": "0a15e36e7f82b6ed45af6fa9759f29b40dcd965d",
12
+ "kind": "dsh-source",
13
+ "paths": [
14
+ "packages/host/open-in-app/",
15
+ "packages/util/native-command/"
16
+ ]
17
+ }
18
+ }
19
+ ]
20
+ },
21
+ "files": [
22
+ "patches/*.patch"
23
+ ],
24
+ "license": "MIT",
25
+ "name": "@sparkelf/dsh-patch-wsl-native-open",
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "repository": {
30
+ "directory": "patches/npm/wsl-native-open",
31
+ "type": "git",
32
+ "url": "git+https://github.com/SparkElf/deepseek-harness-plus.git"
33
+ },
34
+ "type": "module",
35
+ "version": "0.2.0-rc.5"
36
+ }
@@ -0,0 +1,259 @@
1
+ diff --git a/packages/host/open-in-app/src/icons.ts b/packages/host/open-in-app/src/icons.ts
2
+ index 6908ce7..570dc45 100644
3
+ --- a/packages/host/open-in-app/src/icons.ts
4
+ +++ b/packages/host/open-in-app/src/icons.ts
5
+ @@ -97,6 +97,41 @@ const EXTRACT_ICON_PS1 = [
6
+ '',
7
+ ].join('\n')
8
+
9
+ +/**
10
+ + * Translate POSIX paths for the Windows interpreter when this host is WSL.
11
+ + *
12
+ + * `Convert-Path` and `System.Drawing` reject a Linux path: the extension host maps
13
+ + * `/mnt/c/...` itself, but PowerShell running as a Windows process resolves
14
+ + * `\\wsl.localhost\...` and refuses to treat it as a file. `wslpath -w` produces the
15
+ + * form it accepts. A non-WSL host returns its paths unchanged.
16
+ + *
17
+ + * @param paths - absolute POSIX paths to hand to the interpreter.
18
+ + * @param timeoutMs - per-command deadline for the translation.
19
+ + * @param internals - platform and runner hooks for deterministic tests.
20
+ + * @returns the translated paths, or null when a translation failed.
21
+ + */
22
+ +async function windowsPaths(
23
+ + paths: readonly string[], timeoutMs: number, internals: ResolvedInternals,
24
+ +): Promise<string[] | null> {
25
+ + if (!isWslHost(internals)) return [...paths]
26
+ + const translated: string[] = []
27
+ + for (const path of paths) {
28
+ + const windows = await output('wslpath', ['-w', path], timeoutMs, internals)
29
+ + const trimmed = windows?.replace(/[\r\n]+$/u, '') ?? ''
30
+ + if (trimmed === '') return null
31
+ + translated.push(trimmed)
32
+ + }
33
+ + return translated
34
+ +}
35
+ +
36
+ +/** Whether this Host is a WSL distribution, whose Windows desktop takes the path. */
37
+ +function isWslHost(internals: ResolvedInternals): boolean {
38
+ + if (internals.platform !== 'linux') return false
39
+ + const env = internals.env
40
+ + if ((env.WSL_DISTRO_NAME ?? '') !== '' || (env.WSL_INTEROP ?? '') !== '') return true
41
+ + return internals.osRelease.toLowerCase().includes('microsoft')
42
+ +}
43
+ +
44
+ /** Extract one Windows executable's associated icon as a 32px PNG. */
45
+ async function extractExecutableIconPng(
46
+ executablePath: string, timeoutMs: number, internals: ResolvedInternals,
47
+ @@ -106,8 +141,12 @@ async function extractExecutableIconPng(
48
+ const script = join(workDir, 'extract-icon.ps1')
49
+ const outPng = join(workDir, 'icon.png')
50
+ await writeFile(script, EXTRACT_ICON_PS1, 'utf8')
51
+ + // Windows PowerShell reads Windows paths, so a WSL host translates all three before
52
+ + // handing them over; the unmounted caller passes them through unchanged.
53
+ + const args = await windowsPaths([script, executablePath, outPng], timeoutMs, internals)
54
+ + if (args === null) return null
55
+ const ran = await output('powershell.exe', [
56
+ - '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', script, executablePath, outPng,
57
+ + '-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-File', ...args,
58
+ ], timeoutMs, internals)
59
+ if (ran === null) return null
60
+ try {
61
+ @@ -192,6 +231,13 @@ export async function extractAppIcon(
62
+ ): Promise<OpenInAppIcon | null> {
63
+ const completed = resolveInternals(internals)
64
+ if (completed.platform === 'linux') {
65
+ + // A resolved executable icon takes precedence on Linux: WSL reaches the Windows
66
+ + // shell open and its Explorer icon lives in the executable, while the desktop-entry
67
+ + // lookup below has nothing to read on a host with no Linux desktop at all.
68
+ + if (resolved.icon?.kind === 'executable') {
69
+ + const bytes = await extractExecutableIconPng(resolved.icon.path, timeoutMs, completed)
70
+ + return bytes === null ? null : { bytes, contentType: 'image/png' }
71
+ + }
72
+ const desktopId = specFor(app, completed.platform)?.desktopId
73
+ return desktopId === undefined ? null : extractLinuxIcon(desktopId, completed)
74
+ }
75
+ diff --git a/packages/host/open-in-app/src/resolver.ts b/packages/host/open-in-app/src/resolver.ts
76
+ index 7ab964b..8ea7318 100644
77
+ --- a/packages/host/open-in-app/src/resolver.ts
78
+ +++ b/packages/host/open-in-app/src/resolver.ts
79
+ @@ -14,10 +14,10 @@
80
+
81
+ import { spawn } from 'node:child_process'
82
+ import { readdir, readFile, stat } from 'node:fs/promises'
83
+ -import { homedir, platform as osPlatform } from 'node:os'
84
+ +import { homedir, release as osRelease, platform as osPlatform } from 'node:os'
85
+ import { dirname, isAbsolute, join } from 'node:path'
86
+ import {
87
+ - canOpenNativePath, openNativePath, runNativeCommand, type NativeCommandRunner,
88
+ + canOpenLinuxDesktop, canOpenNativePath, openNativePath, runNativeCommand, type NativeCommandRunner,
89
+ } from '@deepseek-ai/dsh-native-command'
90
+ import { scrubbedParentEnv } from '@deepseek-ai/dsh-subprocess'
91
+ import {
92
+ @@ -98,6 +98,8 @@ export const launchDetachedApp: OpenInAppLauncher = (command, args, options) =>
93
+ /** Injectable platform facts for deterministic tests. */
94
+ export interface OpenInAppInternals {
95
+ platform?: NodeJS.Platform
96
+ + /** Kernel release override; distinguishes WSL from desktop Linux without a probe. */
97
+ + osRelease?: string
98
+ /** SSH launch fact from the inherited process layer, independent of `.env` values. */
99
+ ssh?: boolean
100
+ /** Bundle-directory roots replacing `/Applications` and `~/Applications`. */
101
+ @@ -115,6 +117,7 @@ export interface OpenInAppInternals {
102
+ /** Platform facts after the one explicit defaulting step at each public entry. */
103
+ export interface ResolvedInternals {
104
+ platform: NodeJS.Platform
105
+ + osRelease: string
106
+ ssh: boolean
107
+ applicationRoots: readonly string[]
108
+ env: Readonly<Record<string, string | undefined>>
109
+ @@ -140,6 +143,7 @@ export function resolveInternals(internals: OpenInAppInternals): ResolvedInterna
110
+ }
111
+ return {
112
+ platform: internals.platform ?? osPlatform(),
113
+ + osRelease: internals.osRelease ?? osRelease(),
114
+ ssh: internals.ssh ?? false,
115
+ applicationRoots: internals.applicationRoots ?? ['/Applications', join(home, 'Applications')],
116
+ env: internals.env ?? process.env,
117
+ @@ -467,6 +471,14 @@ export function specFor(app: OpenInAppApp, platform: NodeJS.Platform): OpenInApp
118
+ : undefined
119
+ }
120
+
121
+ +/**
122
+ + * Explorer icon for the Windows shell open a WSL host resolves to.
123
+ + *
124
+ + * \`SystemRoot\` is unset inside WSL, so the mounted drive is named directly; the
125
+ + * command-line \`openWindowsPath\` reaches the same interpreter through the same mount.
126
+ + */
127
+ +const WINDOWS_EXPLORER_ICON = '/mnt/c/Windows/explorer.exe'
128
+ +
129
+ /** Icon source for a resolved executable: Windows extracts from the binary itself. */
130
+ function executableIcon(path: string, internals: ResolvedInternals): OpenInAppIconSource | undefined {
131
+ return internals.platform === 'win32' ? { kind: 'executable', path } : undefined
132
+ @@ -518,10 +530,29 @@ async function locate(
133
+ }
134
+ }
135
+ case 'cli': {
136
+ - if (locator.requiresDesktop === true && !canOpenNativePath({
137
+ - platform: internals.platform,
138
+ - env: { ...internals.env },
139
+ - })) return null
140
+ + // A desktop CLI locator names a program that draws on the host's own Linux
141
+ + // desktop. WSL has no such program, but it does have the Windows desktop, so
142
+ + // the entry stays and resolves to the Windows shell open instead of a launcher
143
+ + // this host cannot run.
144
+ + if (locator.requiresDesktop === true) {
145
+ + const facts = {
146
+ + platform: internals.platform,
147
+ + osRelease: internals.osRelease,
148
+ + env: { ...internals.env },
149
+ + }
150
+ + const linuxDesktop = canOpenLinuxDesktop(facts)
151
+ + // WSL answers no here yet can still open a path, because the Windows desktop
152
+ + // takes it. That combination is what selects the shell open below; a headless
153
+ + // Linux host answers no to both and keeps dropping the entry.
154
+ + if (!linuxDesktop && canOpenNativePath(facts)) {
155
+ + const iconPath = expandCandidate(WINDOWS_EXPLORER_ICON, internals)
156
+ + return {
157
+ + launch: { kind: 'shell-open' },
158
+ + icon: iconPath === null ? undefined : { kind: 'executable', path: iconPath },
159
+ + }
160
+ + }
161
+ + if (!linuxDesktop) return null
162
+ + }
163
+ const found = await internals.resolveExecutable(locator.name)
164
+ return found === null
165
+ ? null
166
+ diff --git a/packages/util/native-command/src/index.ts b/packages/util/native-command/src/index.ts
167
+ index a2e23a3..f1ea7c6 100644
168
+ --- a/packages/util/native-command/src/index.ts
169
+ +++ b/packages/util/native-command/src/index.ts
170
+ @@ -6,6 +6,7 @@
171
+ export { runNativeCommand } from './runner.ts'
172
+ export type { NativeCommandRunner } from './runner.ts'
173
+ export {
174
+ + canOpenLinuxDesktop,
175
+ canOpenNativePath,
176
+ nativeFileManager,
177
+ revealNativePath,
178
+ diff --git a/packages/util/native-command/src/path-opener.ts b/packages/util/native-command/src/path-opener.ts
179
+ index 8f3443b..79e7ace 100644
180
+ --- a/packages/util/native-command/src/path-opener.ts
181
+ +++ b/packages/util/native-command/src/path-opener.ts
182
+ @@ -99,13 +99,49 @@ function isWsl(internals: PathOpenerInternals): boolean {
183
+ return (internals.osRelease ?? osRelease()).toLowerCase().includes('microsoft')
184
+ }
185
+
186
+ +/**
187
+ + * Windows PowerShell locations to try when PATH carries no Windows entry.
188
+ + *
189
+ + * WSL injects its Windows directories into PATH only for the processes it starts
190
+ + * interactively, so a service, a scheduled job, or any process started after that
191
+ + * injection was lost resolves nothing by name. The interpreter is still present on
192
+ + * the volume the distribution mounts, and naming it there is what keeps the Windows
193
+ + * desktop reachable from those processes.
194
+ + */
195
+ +const WINDOWS_POWERSHELL_CANDIDATES = [
196
+ + '/mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe',
197
+ + '/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe',
198
+ +] as const
199
+ +
200
+ /** Open one Windows-resolvable path through its registered desktop application. */
201
+ async function openWindowsPath(path: string, signal: AbortSignal, run: PathOpenerRunner): Promise<void> {
202
+ - await run('powershell.exe', [
203
+ + const args = [
204
+ '-NoProfile',
205
+ '-Command',
206
+ `Invoke-Item -LiteralPath ${powershellLiteral(path)}`,
207
+ - ], signal)
208
+ + ]
209
+ + try {
210
+ + await run('powershell.exe', args, signal)
211
+ + return
212
+ + } catch (error) {
213
+ + // A missing executable is the one failure a mounted path can answer; a non-zero
214
+ + // exit from PowerShell itself is a real launch failure and must stay reported.
215
+ + if (!isMissingExecutable(error)) throw error
216
+ + }
217
+ + for (const candidate of WINDOWS_POWERSHELL_CANDIDATES) {
218
+ + try {
219
+ + await run(candidate, args, signal)
220
+ + return
221
+ + } catch (error) {
222
+ + if (!isMissingExecutable(error)) throw error
223
+ + }
224
+ + }
225
+ + throw new Error('Windows PowerShell is not reachable from this WSL mount')
226
+ +}
227
+ +
228
+ +/** Whether a spawn failed because the executable could not be found at all. */
229
+ +function isMissingExecutable(error: unknown): boolean {
230
+ + return (error as { code?: unknown } | null | undefined)?.code === 'ENOENT'
231
+ }
232
+
233
+ /** Translate a WSL path before handing it to the Windows desktop. */
234
+ @@ -154,6 +190,25 @@ async function openNativePathWithIntent(
235
+ throw new Error(`native path opener is unsupported on ${platform}`)
236
+ }
237
+
238
+ +/**
239
+ + * Whether this host can reach a Linux desktop application.
240
+ + *
241
+ + * WSL hands every path to the Windows desktop, so a Linux GUI program such as
242
+ + * `xdg-open` is not what opens anything there: a surface asking this question
243
+ + * wants to know whether a Linux display is attached, and WSL is not one even
244
+ + * though it can open a path.
245
+ + *
246
+ + * @param internals - platform and environment seam for deterministic tests.
247
+ + * @returns true when a Linux desktop application can receive the path.
248
+ + */
249
+ +export function canOpenLinuxDesktop(internals: PathOpenerInternals = {}): boolean {
250
+ + const platform = internals.platform ?? process.platform
251
+ + if (platform !== 'linux') return false
252
+ + if (isWsl(internals)) return false
253
+ + const env = internals.env ?? process.env
254
+ + return present(env.DISPLAY) || present(env.WAYLAND_DISPLAY)
255
+ +}
256
+ +
257
+ /**
258
+ * Whether {@link openNativePath} plausibly reaches a desktop on this host.
259
+ *