dsh-wsl-desktop 0.3.4 → 0.3.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/lib/icon.svg ADDED
@@ -0,0 +1,24 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="64" height="64" viewBox="0 0 64 64" role="img" aria-label="DSH WSL Desktop">
2
+ <!-- W monogram. The polyline's apex/valleys are chosen so the STROKED bounds
3
+ (8.5..55.5 x 13.5..50.5) center on (32,32): the mark is optically centered,
4
+ not merely its path data. Keep the 7px stroke when editing the path, and
5
+ re-center against those bounds rather than the raw vertices. -->
6
+ <defs>
7
+ <linearGradient id="wsl-base" x1="0" y1="0" x2="0" y2="1">
8
+ <stop offset="0" stop-color="#151D31"/>
9
+ <stop offset="1" stop-color="#0B111E"/>
10
+ </linearGradient>
11
+ <radialGradient id="wsl-glow" gradientUnits="userSpaceOnUse" cx="52" cy="4" r="44">
12
+ <stop offset="0" stop-color="#38BDF8" stop-opacity=".32"/>
13
+ <stop offset="1" stop-color="#38BDF8" stop-opacity="0"/>
14
+ </radialGradient>
15
+ <linearGradient id="wsl-mark" x1="12" y1="17" x2="52" y2="47" gradientUnits="userSpaceOnUse">
16
+ <stop offset="0" stop-color="#38BDF8"/>
17
+ <stop offset="1" stop-color="#4D6BFE"/>
18
+ </linearGradient>
19
+ </defs>
20
+ <rect width="64" height="64" rx="15" fill="url(#wsl-base)"/>
21
+ <rect width="64" height="64" rx="15" fill="url(#wsl-glow)"/>
22
+ <path d="M12 17 L22.5 47 L32 25 L41.5 47 L52 17" fill="none" stroke="url(#wsl-mark)" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/>
23
+ <rect x=".75" y=".75" width="62.5" height="62.5" rx="14.25" fill="none" stroke="#7DD3FC" stroke-opacity=".22" stroke-width="1.5"/>
24
+ </svg>
package/lib/wsl/preset.js CHANGED
@@ -49,6 +49,48 @@ export const WORLD_MODULE_NAMES = new Set([
49
49
  '@deepseek-ai/dsh-terminal',
50
50
  ])
51
51
 
52
+ /**
53
+ * Tool-family modules whose survival in a WSL preset is a RECORDED VERDICT.
54
+ *
55
+ * The complement of WORLD_MODULE_NAMES inside the tool family, and the reason it
56
+ * exists: the removal rule is a closed list, so a tool-family row this plugin had
57
+ * never seen was neither removed NOR reported — it was copied into the WSL variant
58
+ * in silence, which is the leak shape this transform exists to prevent. A future
59
+ * DSH release that adds a host-execution tool row would have leaked exactly there,
60
+ * with every suite still green.
61
+ *
62
+ * Every name here was read off a shipped preset and judged world-agnostic: it
63
+ * neither runs host commands nor reaches host files on the model's behalf. A row
64
+ * outside BOTH sets is kept — the transform must not guess a row away from a preset
65
+ * — but reported through `unclassified`, so the next upstream tool row is a
66
+ * decision somebody makes rather than a leak nobody sees.
67
+ */
68
+ export const VERDICTED_KEPT_MODULES = new Set([
69
+ '@deepseek-ai/dsh-tool-jobs',
70
+ '@deepseek-ai/dsh-tool-schedule',
71
+ '@deepseek-ai/dsh-tool-skill',
72
+ '@deepseek-ai/dsh-skill-filesystem',
73
+ '@deepseek-ai/dsh-command-goal',
74
+ '@deepseek-ai/dsh-tool-goal',
75
+ '@deepseek-ai/dsh-tool-subagent-control',
76
+ '@deepseek-ai/dsh-tool-subagent',
77
+ '@deepseek-ai/dsh-tool-workflow',
78
+ '@deepseek-ai/dsh-tool-ask-user',
79
+ '@deepseek-ai/dsh-tool-todo',
80
+ '@deepseek-ai/dsh-tool-web',
81
+ '@deepseek-ai/dsh-tool-cordis',
82
+ '@deepseek-ai/dsh-tool-present',
83
+ ])
84
+
85
+ /**
86
+ * The tool / execution family, by package name.
87
+ *
88
+ * Narrow on purpose: it decides which rows need a VERDICT, and a family wide enough
89
+ * to catch the persona, instruction, plan-mode or compaction rows would redden the
90
+ * acceptance gate over rows whose survival was never in question.
91
+ */
92
+ const TOOL_FAMILY = /^@deepseek-ai\/dsh-(?:tool-[a-z0-9-]+|terminal[a-z0-9-]*|skill-filesystem)$/u
93
+
52
94
  /** Module extensions a specifier may carry where it names the package itself. */
53
95
  const MODULE_SUFFIX = /\.(?:[cm]?js)$/u
54
96
 
@@ -76,6 +118,26 @@ function specifierSegments(specifier) {
76
118
  return readings.flatMap((reading) => reading.split(/[\\/]/u))
77
119
  }
78
120
 
121
+ /**
122
+ * Every `@scope/name` package reading a specifier carries.
123
+ *
124
+ * The one place the package identity is derived, shared by the removal predicate
125
+ * and the verdict predicate so the two cannot disagree about which package a
126
+ * spelling names — the drift that made the earlier basename match miss a subpath.
127
+ * @param {unknown} specifier - the row's module name.
128
+ * @returns {string[]} each scoped pair the specifier carries, in path order.
129
+ */
130
+ function scopedPackages(specifier) {
131
+ if (typeof specifier !== 'string' || specifier.length === 0) return []
132
+ const segments = specifierSegments(specifier)
133
+ const out = []
134
+ for (let index = 0; index + 1 < segments.length; index += 1) {
135
+ if (!segments[index].startsWith('@')) continue
136
+ out.push(`${segments[index]}/${segments[index + 1].replace(MODULE_SUFFIX, '')}`)
137
+ }
138
+ return out
139
+ }
140
+
79
141
  /**
80
142
  * Whether one row `name` loads a host-execution-world module.
81
143
  *
@@ -100,13 +162,33 @@ function specifierSegments(specifier) {
100
162
  export function isHostWorldModule(name) {
101
163
  if (typeof name !== 'string' || name.length === 0) return false
102
164
  if (WORLD_MODULE_NAMES.has(name)) return true
103
- const segments = specifierSegments(name)
104
- for (let index = 0; index + 1 < segments.length; index += 1) {
105
- if (!segments[index].startsWith('@')) continue
106
- const scoped = `${segments[index]}/${segments[index + 1].replace(MODULE_SUFFIX, '')}`
107
- if (WORLD_MODULE_NAMES.has(scoped)) return true
108
- }
109
- return false
165
+ return scopedPackages(name).some((pkg) => WORLD_MODULE_NAMES.has(pkg))
166
+ }
167
+
168
+ /**
169
+ * Whether a row's module belongs to the family that needs a verdict.
170
+ * @param {unknown} name - the row's module name.
171
+ * @returns {boolean} true when the module is a tool-family package.
172
+ */
173
+ export function isToolFamilyModule(name) {
174
+ if (typeof name !== 'string' || name.length === 0) return false
175
+ return [name, ...scopedPackages(name)].some((candidate) => TOOL_FAMILY.test(candidate))
176
+ }
177
+
178
+ /**
179
+ * Whether this plugin holds an explicit verdict for a row's module.
180
+ *
181
+ * True for both verdicts — removed (WORLD_MODULE_NAMES) and deliberately kept
182
+ * (VERDICTED_KEPT_MODULES) — because the question it answers is "has this row been
183
+ * decided?", not "is it dropped?".
184
+ * @param {unknown} name - the row's module name.
185
+ * @returns {boolean} true when the module carries a verdict.
186
+ */
187
+ export function hasClassifierVerdict(name) {
188
+ if (typeof name !== 'string' || name.length === 0) return false
189
+ return [name, ...scopedPackages(name)].some((candidate) => (
190
+ WORLD_MODULE_NAMES.has(candidate) || VERDICTED_KEPT_MODULES.has(candidate)
191
+ ))
110
192
  }
111
193
 
112
194
  /**
@@ -224,12 +306,12 @@ function cloneRow(row) {
224
306
  * @param {string} options.fsPath - module specifier of the WSL filesystem provider.
225
307
  * @param {string | undefined} options.distro - distribution for paths that do not name one.
226
308
  * @param {string | undefined} options.sourceDir - directory the source preset lives in.
227
- * @returns {{ plugins: object[], removed: string[], unclassified: { id: string | null, name: unknown }[] }} the variant rows, what was dropped, and the rows kept without a classifier verdict.
309
+ * @returns {{ plugins: object[], removed: string[], unclassified: { id: string | null, name: unknown }[] }} the variant rows, what was dropped, and the rows kept without a classifier verdict — an unreadable specifier, or a tool-family module that is neither removed nor verdicted-kept.
228
310
  */
229
311
  export function buildVariantPlugins(basePlugins, { subprocessPath, shellPath, fsPath, distro, sourceDir }) {
230
312
  const kept = []
231
313
  const removed = []
232
- /** Rows kept without a classifier verdict — see the module-name branch in prune(). */
314
+ /** Rows kept without a classifier verdict — both shapes the prune() branches record. */
233
315
  const unclassified = []
234
316
  let includeEditor = false
235
317
  /** Config carried by the pruned host-world tool rows, keyed by row id. */
@@ -267,6 +349,14 @@ export function buildVariantPlugins(basePlugins, { subprocessPath, shellPath, fs
267
349
  toolConfig.set('str-replace-editor', cloneRow({ config: row.config }).config)
268
350
  }
269
351
  return null
352
+ } else if (isToolFamilyModule(row.name) && !hasClassifierVerdict(row.name)) {
353
+ // Readable specifier, tool family, NO verdict: kept and reported. The removal
354
+ // rule is a closed list, so without this branch a tool row this plugin has
355
+ // never seen was copied into the WSL variant in silence — the leak shape this
356
+ // transform exists to prevent, and the one a new upstream tool row would have
357
+ // taken. Kept rather than removed for the same reason as the unreadable case
358
+ // above: the transform must not guess a row away from a preset.
359
+ unclassified.push({ id: row.id ?? null, name: row.name })
270
360
  }
271
361
  // The registry stores the caller's objects BY REFERENCE — mutating a row
272
362
  // here would pollute the shared base definition. Clone per row;
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "dsh-wsl-desktop",
3
- "version": "0.3.4",
3
+ "version": "0.3.5",
4
4
  "type": "module",
5
5
  "main": "lib/index.js",
6
+ "icon": "./lib/icon.svg",
6
7
  "exports": {
7
8
  ".": "./lib/index.js",
8
9
  "./client": "./lib/client.js",