dsh-plugin-wsl-env 0.13.1 → 0.13.2

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/CHANGELOG.md CHANGED
@@ -16,6 +16,73 @@ were part of the record.
16
16
 
17
17
  ## [Unreleased]
18
18
 
19
+ ## [0.13.2] - 2026-10-26
20
+
21
+ This release tracks DeepSeek Harness `0.2.1-rc.3`: the peer declarations become
22
+ floor ranges so a newer harness installs the plugin unchanged, and the
23
+ lockfile's transitive `undici` moves past a security advisory that Dependabot
24
+ could not clear on its own.
25
+
26
+ ### Fixed
27
+
28
+ - **`undici` in the lockfile moves to the security-fixed `8.12.3` via a pnpm
29
+ override.** `undici` rides in only as an exact transitive pin of
30
+ `@deepseek-ai/dsh-http-proxy` — a package upstream controls — so Dependabot's
31
+ security job could resolve no patched version (it reports "the latest
32
+ possible version … is 8.11.2" and fails). `pnpm-workspace.yaml` now carries
33
+ `overrides: { undici: '>=8.12.3' }` (the pnpm 11 settings home; the
34
+ `package.json` `pnpm` field is no longer read), the lockfile swaps the one
35
+ package, and the override comment records that it is retired when upstream
36
+ moves the pin. Nothing under `src/` reads `undici`; the check chain is
37
+ unaffected.
38
+
39
+ ### Changed
40
+
41
+ - **The peer declarations become floor ranges, so a newer harness installs
42
+ the plugin unchanged.** The `@deepseek-ai/dsh-*` entries in
43
+ `peerDependencies` move from the exact pin to `>=0.2.1-rc.3`, and
44
+ `@deepseek-ai/cordis` / `@deepseek-ai/schemastery` widen from `~` to `^`
45
+ (`^4.0.5`, `^3.18.5`). An app that ships a newer harness release now
46
+ satisfies the peers without waiting for a plugin release — same-line rc
47
+ bumps and every stable release install cleanly; only a brand-new
48
+ prerelease line (`0.2.2-rc.1` against `>=0.2.1-rc.3`) still warns, because
49
+ semver admits a prerelease only against a comparator on its own tuple and
50
+ no range can promise an unpublished line. That residue is what the weekly
51
+ peer-canary is for: it still resolves the `next` dist-tag into a scratch
52
+ tree and flags when the line moves past a floor — the signal to re-run the
53
+ `docs/PEER-PARITY.md` comparison and raise the floor in the commit that
54
+ matches the harness release. The devDependencies keep their exact pins, so
55
+ the gates stay reproducible and the parity audit keeps naming one line.
56
+ `scripts/doctor.mjs` learned the new shapes (`peerSatisfied` now reads `>=`
57
+ and `^`, and its `compareVersions` orders prerelease identifiers by semver
58
+ §11 — `rc.10` above `rc.9`), `scripts/peer-canary.mjs` parses and preserves
59
+ the shapes, and `AGENTS.md`, `CONTRIBUTING.md`, `SUPPORT.md` and
60
+ `docs/PEER-PARITY.md` state the new policy.
61
+
62
+ - **The dev pin line moves to DeepSeek Harness `0.2.1-rc.3`.** Every
63
+ `@deepseek-ai/dsh-*` pin in `devDependencies` goes from `0.2.1-rc.2` to
64
+ `0.2.1-rc.3` — the release the `next` dist-tag resolves to — and
65
+ `pnpm-lock.yaml` refreshes with it. `@deepseek-ai/cordis` and
66
+ `@deepseek-ai/schemastery` stay at `4.0.5` / `3.18.5`: upstream's `vendor/`
67
+ did not change on this line.
68
+
69
+ No module under `src/` changed, and that is the finding. The three peers
70
+ this plugin subclasses or replicates ship byte-identical `lib/index.js`
71
+ files on rc.3 — `dsh-fs-local` (the fsio dialect, still 909 lines), `dsh-fs`
72
+ (the `FsVersion` brand) and `dsh-bash-local` (the spill machinery) — so
73
+ every line reference in `docs/PEER-PARITY.md` still names the same code;
74
+ only the tarballs' digests changed, and the parity doc now carries the
75
+ `0.2.1-rc.3` ones with the re-check dated 2026-10-26. That byte-identity is
76
+ also the evidence behind the new peer floor: the same code answers above
77
+ rc.3. Upstream's release notes for the line are UI- and LLM-side
78
+ (navigation entry customization, analytics, the official `/models`
79
+ listing): the one composition-visible change is a new `entry-customization`
80
+ UI row in the app's own `cordis.patch.yml`, which rides with the app's
81
+ bundle rather than this plugin's layer. `preset-wsl` needed no
82
+ regeneration — `packages/bundle/web-app/presets/standard.patch.yml` is
83
+ byte-identical on rc.3 — and the provenance comment in `cordis.patch.yml`
84
+ now names the rc.3 tree.
85
+
19
86
  ## [0.13.1] - 2026-10-19
20
87
 
21
88
  A patch release restoring `wsl-shell-env`: the component mounts again, and the
package/CONTRIBUTING.md CHANGED
@@ -23,13 +23,24 @@ Developing and running the gates do have dependencies. The tests import pinned
23
23
  algorithm, and the boot test starts a row through the real Loader — so a fresh
24
24
  checkout needs one `pnpm install` before the gates pass.
25
25
 
26
- Those pins are deliberate, and they move with the harness rather than on their
27
- own: the `@deepseek-ai/dsh-*` peers sit at `0.2.1-rc.2`, `@deepseek-ai/cordis`
28
- at `~4.0.5` and `@deepseek-ai/schemastery` at `~3.18.5`. The plugin subclasses
29
- peer classes and mirrors peer protocol, so raising one pin alone is how the
30
- seams drift: bump them in the commit that matches a harness release, re-run the
31
- comparison `docs/PEER-PARITY.md` describes, and let the gate chain and the
32
- probes confirm the result.
26
+ Those pins are deliberate, and they split by role. The `devDependencies` stay
27
+ exact so the gates and the `docs/PEER-PARITY.md` audit name one reproducible
28
+ line. The `peerDependencies` are floors — `>=` for the `@deepseek-ai/dsh-*`
29
+ packages, caret for `@deepseek-ai/cordis` and `@deepseek-ai/schemastery` — so
30
+ an app that ships a newer harness keeps installing the plugin without waiting
31
+ for a plugin release. A floor is still raised on purpose, not by drift: it
32
+ moves in the commit that matches a harness release, with the parity comparison
33
+ and the gate chain re-run, and the weekly peer-canary
34
+ (`.github/workflows/peer-canary.yml`) resolves the `next` dist-tag into a
35
+ scratch tree and flags when the line has moved past the floor. One semver
36
+ limit is worth knowing: a floor range cannot name future prerelease lines —
37
+ `0.2.2-rc.1` does not satisfy `>=0.2.1-rc.3`, because the spec admits a
38
+ prerelease only against a comparator on its own `[major, minor, patch]` tuple
39
+ — so a brand-new rc line warns until the floor rises; same-line rcs and every
40
+ stable release never do.
41
+
42
+ The plugin subclasses peer classes and mirrors peer protocol, so the range
43
+ says nothing about the parity claims: those are re-verified per floor raise.
33
44
 
34
45
  pnpm runs the scripts in this repository, pinned through the `packageManager`
35
46
  field (corepack resolves it). Treat `pnpm run <script>` as the reference
package/SUPPORT.md CHANGED
@@ -6,7 +6,7 @@
6
6
  |---|---|
7
7
  | Operating system | Windows 10 and Windows 11 |
8
8
  | Linux side | WSL2 with at least one distro, and systemd as PID 1 with a lingering user manager (`loginctl enable-linger`) for the shipped session runtime. Set `sessionSandbox: false` on the preset's `wsl-shell` to keep the per-command sandbox instead |
9
- | DSH packages | the `0.2.1-rc.2`-era packages listed in `peerDependencies` |
9
+ | DSH packages | the `0.2.1-rc.3`-or-newer packages the `peerDependencies` floors declare; an app on a newer harness installs unchanged |
10
10
 
11
11
  The DSH row is checked weekly: `.github/workflows/peer-canary.yml` resolves the
12
12
  `next` dist-tag of every pinned peer, installs that line into a scratch copy and
package/cordis.patch.yml CHANGED
@@ -185,7 +185,8 @@
185
185
  # Generated from the shipped standard preset so the embedded plan-mode
186
186
  # section and every nested group survive byte-for-byte: this copy is
187
187
  # `packages/bundle/web-app/presets/standard.patch.yml` of the
188
- # `dsh-v0.2.1-rc.2` source tree, changed in exactly three ways —
188
+ # `dsh-v0.2.1-rc.3` source tree (unchanged since `0.2.1-rc.2`), changed
189
+ # in exactly three ways —
189
190
  # 1. every shipped row mounts inside `wsl-env`, so the preset's tools see
190
191
  # the distro's shell and fs (the realm is declared on the group);
191
192
  # 2. `tool-bash` is enabled and `tool-pwsh` disabled outright, where the
@@ -7,18 +7,20 @@ same refusal dialect on both substrates. The **read path** holds end to end;
7
7
  the **edit path** carries two declared divergences, listed at the end. This
8
8
  file records the verification against the real peers.
9
9
 
10
- - Peers: `@deepseek-ai/dsh-fs-local@0.2.1-rc.2` (the exact version
11
- `package.json` pins), plus `@deepseek-ai/dsh-fs@0.2.1-rc.2` for the
10
+ - Peers: `@deepseek-ai/dsh-fs-local@0.2.1-rc.3` (the exact version the
11
+ devDependencies pin, and the floor the peer range `>=0.2.1-rc.3` was
12
+ verified against), plus `@deepseek-ai/dsh-fs@0.2.1-rc.3` for the
12
13
  `FsVersion` brand the local package imports. Divergence 2 is measured
13
- against a third peer, `@deepseek-ai/dsh-bash-local@0.2.1-rc.2` — the shell
14
+ against a third peer, `@deepseek-ai/dsh-bash-local@0.2.1-rc.3` — the shell
14
15
  executor carries the collect-budget/spill machinery; `spill` and
15
16
  `maxSpillBytes` do not appear in `dsh-fs-local` at all.
16
- - The `0.2.1-rc.2` move leaves every line reference below intact: all three
17
- peers' shipped `lib/index.js` are **byte-identical** to the `0.2.0-rc.2`
17
+ - The `0.2.1-rc.3` move leaves every line reference below intact: all three
18
+ peers' shipped `lib/index.js` are **byte-identical** to the `0.2.1-rc.2`
18
19
  ones (`cmp` clean; `dsh-fs-local`'s is still 909 lines), so only the
19
- tarballs' digests changed. `dsh-fs@0.2.1-rc.2` did drop its unpublished
20
- `./invariant` export subpath; this plugin imports neither it nor
21
- `dsh-invariants`.
20
+ tarballs' digests changed — upstream shipped the manifests' version fields
21
+ and nothing else in these packages. `dsh-fs@0.2.1-rc.3` still has no
22
+ `./invariant` export subpath (dropped back at `0.2.1-rc.2`); this plugin
23
+ imports neither it nor `dsh-invariants`.
22
24
  - What "not imported" means: the peer's **fsio module** is not reachable from
23
25
  the package's exports, so `lib/fsio-text.js` reimplements it and imports
24
26
  nothing from it (the export is proposed in `docs/UPSTREAM-FSIO-EXPORT.md`).
@@ -33,24 +35,24 @@ file records the verification against the real peers.
33
35
 
34
36
  ```sh
35
37
  mkdir -p /tmp/peer-audit && cd /tmp/peer-audit
36
- npm pack @deepseek-ai/dsh-fs-local@0.2.1-rc.2 @deepseek-ai/dsh-fs@0.2.1-rc.2 --pack-destination .
37
- openssl dgst -sha512 -binary deepseek-ai-dsh-fs-local-0.2.1-rc.2.tgz | base64 -w0
38
- tar xzf deepseek-ai-dsh-fs-local-0.2.1-rc.2.tgz # -> package/lib/index.js
38
+ npm pack @deepseek-ai/dsh-fs-local@0.2.1-rc.3 @deepseek-ai/dsh-fs@0.2.1-rc.3 --pack-destination .
39
+ openssl dgst -sha512 -binary deepseek-ai-dsh-fs-local-0.2.1-rc.3.tgz | base64 -w0
40
+ tar xzf deepseek-ai-dsh-fs-local-0.2.1-rc.3.tgz # -> package/lib/index.js
39
41
  wc -l package/lib/index.js # expect 909
40
42
  ```
41
43
 
42
44
  The tarballs this audit compared, by content — the same values
43
- `npm view <pkg>@0.2.1-rc.2 dist.integrity` reports:
45
+ `npm view <pkg>@0.2.1-rc.3 dist.integrity` reports:
44
46
 
45
47
  ```
46
- @deepseek-ai/dsh-fs-local@0.2.1-rc.2 sha512-IZPJ12/EpV6fu3CCWtW4w4GthXAKLFjplm4Feoep2HAum7J1aGzHZwiw7ipsPCZiA5/TyK3lDUUAGZ/pcXgRcw==
47
- @deepseek-ai/dsh-fs@0.2.1-rc.2 sha512-Z6d2ZKsAiJc0FaiES8wC66vuUQMGohl9tCc2p3bAsPFYkJ0+L2zTViQ9Y5BArtRe9Br8qhNjNgLlN0jb/uwv1g==
48
+ @deepseek-ai/dsh-fs-local@0.2.1-rc.3 sha512-bB2CwCqvYAdAKl28+5XXMRRq1NBtRmYX3wNOpJ2JlGBm7uj2Aix9Vl9rFig4AQcXXd5Bk738b5KNsOIkbXy+XQ==
49
+ @deepseek-ai/dsh-fs@0.2.1-rc.3 sha512-tENLWe471WlAXMc13J4cns8xU6lFyu1smwpkSKR85vhGr6FyWZhTlpVohZYm66LbwKTmSWiDhd9Urk7VxN9B9w==
48
50
  ```
49
51
 
50
52
  Each row of the table below names the peer's `lib/index.js` line and the
51
53
  literal both sides must produce. Verified 2026-10-03; the read/edit split
52
54
  re-verified 2026-10-06; the whole table re-checked against `0.2.1-rc.2`,
53
- 2026-10-17.
55
+ 2026-10-17 and against `0.2.1-rc.3`, 2026-10-26.
54
56
 
55
57
  ## Verdict
56
58
 
@@ -93,7 +95,7 @@ declared extensions, listed at the end.
93
95
  2. **Per-stream truncation flags.** The EXEC frame carries an output budget;
94
96
  the RES line reports which streams the agent cut, surfaced as honest
95
97
  `truncated` flags. The collect-budget/spill machinery of the shell peer,
96
- `@deepseek-ai/dsh-bash-local@0.2.1-rc.2` (`lib/index.js:30`, `:110`), is the
98
+ `@deepseek-ai/dsh-bash-local@0.2.1-rc.3` (`lib/index.js:30`, `:110`), is the
97
99
  spill-file path we deliberately do not run.
98
100
  3. **The transport around the ops** (ACK, KILL, the confined resident) is
99
101
  protocol this package owns; the peer has no agent.
@@ -120,9 +122,12 @@ declared extensions, listed at the end.
120
122
 
121
123
  ## Maintenance
122
124
 
123
- When `package.json` bumps the peer pins, re-run the comparison above — pack the
124
- new tarballs, check the sha512, and walk the table's peer line references
125
- against the replicas. The fastest canary: stat a file, list its directory, and
125
+ When a peer floor or dev pin moves in `package.json`, re-run the comparison
126
+ above — pack the new tarballs, check the sha512, and walk the table's peer line
127
+ references against the replicas. The weekly peer-canary resolves the `next`
128
+ dist-tag into a scratch tree and runs the gate chain against it, so the signal
129
+ to raise the floor arrives before a user reports a drift. The fastest canary:
130
+ stat a file, list its directory, and
126
131
  compare the versions (`test/fsio-agent.test.mjs` asserts the intra-distro half
127
132
  of this); the strings above are the other half. The split dialect has its own
128
133
  regression: `test/fs-substrate.test.mjs` asserts the missing-file refusal, and
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-plugin-wsl-env",
3
- "version": "0.13.1",
4
- "description": "\u8ba9 DeepSeek Harness \u4ee5 WSL \u5b50\u7cfb\u7edf\u4f5c\u4e3a\u4f1a\u8bdd\u7684\u6267\u884c\u73af\u5883\uff1ashell\u3001\u6587\u4ef6\u8bfb\u5199\u4e0e\u547d\u4ee4\u6267\u884c\u5747\u5728\u5b50\u7cfb\u7edf\u5185\u5b8c\u6210\uff0c\u76ee\u5f55\u9009\u62e9\u5668\u4e0e GUI \u7ec8\u7aef\u540c\u6837\u5b9a\u4f4d\u5230\u5b50\u7cfb\u7edf\u8def\u5f84\uff0c\u5e76\u901a\u8fc7\u5b50\u7cfb\u7edf\u5185\u7684 bubblewrap \u5bf9\u547d\u4ee4\u8fdb\u884c\u6c99\u7bb1\u9694\u79bb",
3
+ "version": "0.13.2",
4
+ "description": "让 DeepSeek Harness 以 WSL 子系统作为会话的执行环境:shell、文件读写与命令执行均在子系统内完成,目录选择器与 GUI 终端同样定位到子系统路径,并通过子系统内的 bubblewrap 对命令进行沙箱隔离",
5
5
  "keywords": [
6
6
  "deepseek-harness",
7
7
  "dsh",
@@ -179,32 +179,32 @@
179
179
  "@deepseek-ai/cordis": "4.0.5",
180
180
  "@deepseek-ai/cordis-plugin-include": "*",
181
181
  "@deepseek-ai/cordis-plugin-loader": "*",
182
- "@deepseek-ai/dsh-bash-local": "0.2.1-rc.2",
183
- "@deepseek-ai/dsh-fs": "0.2.1-rc.2",
184
- "@deepseek-ai/dsh-fs-local": "0.2.1-rc.2",
185
- "@deepseek-ai/dsh-fs-sandbox": "0.2.1-rc.2",
186
- "@deepseek-ai/dsh-host-directory-picker": "0.2.1-rc.2",
187
- "@deepseek-ai/dsh-sandbox": "0.2.1-rc.2",
188
- "@deepseek-ai/dsh-sandbox-policy": "0.2.1-rc.2",
189
- "@deepseek-ai/dsh-subprocess": "0.2.1-rc.2",
190
- "@deepseek-ai/dsh-subprocess-local": "0.2.1-rc.2",
191
- "@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.2",
182
+ "@deepseek-ai/dsh-bash-local": "0.2.1-rc.3",
183
+ "@deepseek-ai/dsh-fs": "0.2.1-rc.3",
184
+ "@deepseek-ai/dsh-fs-local": "0.2.1-rc.3",
185
+ "@deepseek-ai/dsh-fs-sandbox": "0.2.1-rc.3",
186
+ "@deepseek-ai/dsh-host-directory-picker": "0.2.1-rc.3",
187
+ "@deepseek-ai/dsh-sandbox": "0.2.1-rc.3",
188
+ "@deepseek-ai/dsh-sandbox-policy": "0.2.1-rc.3",
189
+ "@deepseek-ai/dsh-subprocess": "0.2.1-rc.3",
190
+ "@deepseek-ai/dsh-subprocess-local": "0.2.1-rc.3",
191
+ "@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.3",
192
192
  "@types/js-yaml": "^4.0.9",
193
193
  "@types/node": "~22.20.5",
194
194
  "js-yaml": "4.3.2",
195
195
  "typescript": "~6.0.3"
196
196
  },
197
197
  "peerDependencies": {
198
- "@deepseek-ai/cordis": "~4.0.5",
199
- "@deepseek-ai/dsh-bash-local": "0.2.1-rc.2",
200
- "@deepseek-ai/dsh-fs": "0.2.1-rc.2",
201
- "@deepseek-ai/dsh-fs-local": "0.2.1-rc.2",
202
- "@deepseek-ai/dsh-host-directory-picker": "0.2.1-rc.2",
203
- "@deepseek-ai/dsh-sandbox": "0.2.1-rc.2",
204
- "@deepseek-ai/dsh-subprocess": "0.2.1-rc.2",
205
- "@deepseek-ai/dsh-subprocess-local": "0.2.1-rc.2",
206
- "@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.2",
207
- "@deepseek-ai/schemastery": "~3.18.5"
198
+ "@deepseek-ai/cordis": "^4.0.5",
199
+ "@deepseek-ai/dsh-bash-local": ">=0.2.1-rc.3",
200
+ "@deepseek-ai/dsh-fs": ">=0.2.1-rc.3",
201
+ "@deepseek-ai/dsh-fs-local": ">=0.2.1-rc.3",
202
+ "@deepseek-ai/dsh-host-directory-picker": ">=0.2.1-rc.3",
203
+ "@deepseek-ai/dsh-sandbox": ">=0.2.1-rc.3",
204
+ "@deepseek-ai/dsh-subprocess": ">=0.2.1-rc.3",
205
+ "@deepseek-ai/dsh-subprocess-local": ">=0.2.1-rc.3",
206
+ "@deepseek-ai/dsh-typert-protocol": ">=0.2.1-rc.3",
207
+ "@deepseek-ai/schemastery": "^3.18.5"
208
208
  },
209
209
  "peerDependenciesMeta": {
210
210
  "@deepseek-ai/cordis": {
@@ -145,19 +145,24 @@ function parseArgs(argv) {
145
145
  }
146
146
 
147
147
  /**
148
- * Whether an installed version satisfies one peer range. The plugin's pins are
149
- * exact or tilde ranges, so those two shapes are all this understands; anything
150
- * else is reported as unchecked rather than guessed at.
148
+ * Whether an installed version satisfies one peer range. The manifest's peer
149
+ * ranges are floor ranges (`>=`) and caret ranges (`^`), and the
150
+ * devDependencies carry exact pins — those three shapes are all this
151
+ * understands; anything else is reported as unchecked rather than guessed at.
152
+ * The floor is the plugin's own compatibility claim, so it compares plainly
153
+ * against the floor version; the semver gate that excludes other lines'
154
+ * prereleases is an installer concern, not this check's.
151
155
  * @param {string} installed - the installed version.
152
156
  * @param {string} range - the declared peer range.
153
157
  * @returns {boolean | undefined} true/false, or undefined for an unsupported shape.
154
158
  */
155
159
  export function peerSatisfied(installed, range) {
156
- if (range.startsWith("~")) {
160
+ if (range.startsWith(">=")) return compareVersions(installed, range.slice(2)) >= 0;
161
+ if (range.startsWith("^")) {
157
162
  const [major, minor, patch] = range.slice(1).split(".");
158
163
  return compareVersions(installed, `${major}.${minor}.${patch ?? "0"}`) >= 0
159
164
  && installed.split(".")[0] === major
160
- && installed.split(".")[1] === minor;
165
+ && (major !== "0" || installed.split(".")[1] === minor);
161
166
  }
162
167
  if (/^[0-9]/.test(range)) return installed === range;
163
168
  return undefined;
@@ -188,7 +193,35 @@ export function compareVersions(left, right) {
188
193
  if (a.pre === b.pre) return 0;
189
194
  if (a.pre === "") return 1;
190
195
  if (b.pre === "") return -1;
191
- return a.pre.localeCompare(b.pre);
196
+ return comparePrerelease(a.pre, b.pre);
197
+ }
198
+
199
+ /**
200
+ * Compare two prerelease strings by semver §11: dot-separated identifiers,
201
+ * numeric ones numerically and below alphanumeric ones, the rest lexically,
202
+ * a longer list above its own prefix. Plain `localeCompare` would order
203
+ * `rc.10` below `rc.9`.
204
+ * @param {string} left - the first prerelease.
205
+ * @param {string} right - the second prerelease.
206
+ * @returns {number} negative, zero or positive like a comparator.
207
+ */
208
+ function comparePrerelease(left, right) {
209
+ const a = left.split(".");
210
+ const b = right.split(".");
211
+ for (let i = 0; i < Math.max(a.length, b.length); i += 1) {
212
+ const x = a[i];
213
+ const y = b[i];
214
+ if (x === undefined) return -1;
215
+ if (y === undefined) return 1;
216
+ if (x === y) continue;
217
+ const numericX = /^\d+$/.test(x);
218
+ const numericY = /^\d+$/.test(y);
219
+ if (numericX && numericY) return Number(x) - Number(y);
220
+ if (numericX) return -1;
221
+ if (numericY) return 1;
222
+ return x < y ? -1 : 1;
223
+ }
224
+ return 0;
192
225
  }
193
226
 
194
227
  /**