dsh-wsl-workspace 0.7.4 → 0.7.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/README.de.md CHANGED
@@ -8,6 +8,8 @@
8
8
  ![alt text](image-3.png)
9
9
  Füge aus der DeepSeek-Harness-Web-GUI einen WSL-Arbeitsbereich hinzu und führe die gesamte Agent-Sitzung — Bash-Befehle und Datei-Lesen/-Schreiben — innerhalb einer lokalen WSL-Distribution mit Linux-Pfaden aus. In WSL muss nichts installiert werden. Die Sitzung kann gleichzeitig auf WSL und Windows zugreifen: Bash-Befehle laufen in der WSL-Distribution, während Windows-Dateien über `/mnt/<laufwerk>` (z. B. `/mnt/c/Users/...`) erreichbar bleiben.
10
10
 
11
+ Die Liste der unterstützten DSH-Versionen steht im Abschnitt Compatibility von [README.md](README.md).
12
+
11
13
  ## Installation
12
14
 
13
15
  Wähle eine der drei folgenden Methoden und starte danach `dsh web` neu:
@@ -17,7 +19,7 @@ Wähle eine der drei folgenden Methoden und starte danach `dsh web` neu:
17
19
  dsh plugin --profile web add dsh-wsl-workspace
18
20
 
19
21
  # 2) GitHub-Repository (enthält das vorgebaute lib/, kein lokaler Build nötig)
20
- dsh plugin --profile web add https://github.com/6Mikao9/dsh-wsl-workspace
22
+ dsh plugin --profile web add https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace
21
23
 
22
24
  # 3) Lokales Verzeichnis (Entwicklung / eigener Gebrauch)
23
25
  dsh plugin --profile web add D:\path\to\dsh-wsl-workspace
package/README.es.md CHANGED
@@ -8,6 +8,8 @@
8
8
  ![alt text](image-3.png)
9
9
  Añade un espacio de trabajo WSL desde la interfaz web de DeepSeek Harness y ejecuta toda la sesión del agente — comandos bash y lectura/escritura de archivos — dentro de una distribución WSL local con rutas en formato Linux. No hay nada que instalar dentro de WSL. La sesión puede acceder a la vez a WSL y a Windows: los comandos bash se ejecutan dentro de la distribución WSL, mientras que los archivos de Windows siguen accesibles vía `/mnt/<unidad>` (por ejemplo `/mnt/c/Users/...`).
10
10
 
11
+ La lista de versiones de DSH compatibles está en la sección Compatibility de [README.md](README.md).
12
+
11
13
  ## Instalación
12
14
 
13
15
  Elige uno de los tres métodos siguientes y reinicia `dsh web`:
@@ -17,7 +19,7 @@ Elige uno de los tres métodos siguientes y reinicia `dsh web`:
17
19
  dsh plugin --profile web add dsh-wsl-workspace
18
20
 
19
21
  # 2) Repositorio de GitHub (incluye lib/ precompilado, sin compilar en local)
20
- dsh plugin --profile web add https://github.com/6Mikao9/dsh-wsl-workspace
22
+ dsh plugin --profile web add https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace
21
23
 
22
24
  # 3) Directorio local (desarrollo / uso personal)
23
25
  dsh plugin --profile web add D:\path\to\dsh-wsl-workspace
package/README.fr.md CHANGED
@@ -8,6 +8,8 @@
8
8
  ![alt text](image-3.png)
9
9
  Ajoutez un espace de travail WSL depuis l'interface web de DeepSeek Harness et exécutez toute la session de l'agent — commandes bash et lectures/écritures de fichiers — dans une distribution WSL locale avec des chemins au format Linux. Aucune installation supplémentaire n'est nécessaire dans WSL. La session peut accéder simultanément à WSL et à Windows : les commandes bash s'exécutent dans la distribution WSL, tandis que les fichiers Windows restent accessibles via `/mnt/<lecteur>` (par exemple `/mnt/c/Users/...`).
10
10
 
11
+ La liste des versions DSH prises en charge se trouve dans la section Compatibility de [README.md](README.md).
12
+
11
13
  ## Installation
12
14
 
13
15
  Choisissez l'une des trois méthodes ci-dessous, puis redémarrez `dsh web` :
@@ -17,7 +19,7 @@ Choisissez l'une des trois méthodes ci-dessous, puis redémarrez `dsh web` :
17
19
  dsh plugin --profile web add dsh-wsl-workspace
18
20
 
19
21
  # 2) Dépôt GitHub (livré avec lib/ pré-construit, aucune compilation locale requise)
20
- dsh plugin --profile web add https://github.com/6Mikao9/dsh-wsl-workspace
22
+ dsh plugin --profile web add https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace
21
23
 
22
24
  # 3) Répertoire local (développement / usage personnel)
23
25
  dsh plugin --profile web add D:\path\to\dsh-wsl-workspace
package/README.ja.md CHANGED
@@ -8,6 +8,8 @@
8
8
  ![alt text](image-3.png)
9
9
  DeepSeek Harness Web GUI から WSL ワークスペースを追加し、エージェントセッション全体(bash コマンドとファイルの読み書き)をローカルの WSL ディストリビューション内で実行します。パスはすべて Linux 形式です。WSL 内への追加インストールは不要です。セッションから WSL と Windows の両方に同時にアクセスできます。bash コマンドは WSL ディストリビューション内で実行され、Windows のファイルは `/mnt/<drive>`(例:`/mnt/c/Users/...`)経由でいつでもアクセスできます。
10
10
 
11
+ 対応する DSH バージョンの一覧は [README.md](README.md) の Compatibility を参照してください。
12
+
11
13
  ## インストール
12
14
 
13
15
  次のいずれかの方法でインストールし、`dsh web` を再起動してください:
@@ -17,7 +19,7 @@ DeepSeek Harness Web GUI から WSL ワークスペースを追加し、エー
17
19
  dsh plugin --profile web add dsh-wsl-workspace
18
20
 
19
21
  # 2) GitHub リポジトリ(事前ビルド済みの lib/ を含むためローカルビルド不要)
20
- dsh plugin --profile web add https://github.com/6Mikao9/dsh-wsl-workspace
22
+ dsh plugin --profile web add https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace
21
23
 
22
24
  # 3) ローカルディレクトリ(開発・自用)
23
25
  dsh plugin --profile web add D:\path\to\dsh-wsl-workspace
package/README.ko.md CHANGED
@@ -8,6 +8,8 @@
8
8
  ![alt text](image-3.png)
9
9
  DeepSeek Harness Web GUI에서 WSL 워크스페이스를 추가하고 에이전트 세션 전체(bash 명령과 파일 읽기/쓰기)를 로컬 WSL 배포판 안에서 실행합니다. 모든 경로는 Linux 형식이며, WSL 내부에 별도로 설치할 것이 없습니다. 세션에서 WSL과 Windows 양쪽에 동시에 접근할 수 있습니다. bash 명령은 WSL 배포판 안에서 실행되고, Windows 파일은 `/mnt/<drive>`(예: `/mnt/c/Users/...`)로 언제든 접근할 수 있습니다.
10
10
 
11
+ 지원하는 DSH 버전 목록은 [README.md](README.md)의 Compatibility 섹션을 참고하세요.
12
+
11
13
  ## 설치
12
14
 
13
15
  아래 세 가지 방법 중 하나를 선택한 뒤 `dsh web`을 다시 시작하세요:
@@ -17,7 +19,7 @@ DeepSeek Harness Web GUI에서 WSL 워크스페이스를 추가하고 에이전
17
19
  dsh plugin --profile web add dsh-wsl-workspace
18
20
 
19
21
  # 2) GitHub 저장소(사전 빌드된 lib/ 포함, 로컬 빌드 불필요)
20
- dsh plugin --profile web add https://github.com/6Mikao9/dsh-wsl-workspace
22
+ dsh plugin --profile web add https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace
21
23
 
22
24
  # 3) 로컬 디렉터리(개발/개인용)
23
25
  dsh plugin --profile web add D:\path\to\dsh-wsl-workspace
package/README.md CHANGED
@@ -16,7 +16,7 @@ Pick one of the three ways below, then restart `dsh web`:
16
16
  dsh plugin --profile web add dsh-wsl-workspace
17
17
 
18
18
  # 2) GitHub repository (ships the prebuilt lib/, no local build required)
19
- dsh plugin --profile web add https://github.com/6Mikao9/dsh-wsl-workspace
19
+ dsh plugin --profile web add https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace
20
20
 
21
21
  # 3) Local directory (development / self-hosted)
22
22
  dsh plugin --profile web add D:\path\to\dsh-wsl-workspace
@@ -24,6 +24,22 @@ dsh plugin --profile web add D:\path\to\dsh-wsl-workspace
24
24
 
25
25
  After restarting `dsh web`, a W button appears beside Settings at the sidebar foot.
26
26
 
27
+ ## Compatibility
28
+
29
+ This build declares these DSH releases, each verified on an isolated instance (its own
30
+ `DSH_HOME`, dependencies pinned to that release, the full check suite):
31
+
32
+ `0.1.0-rc.7` · `0.1.0-rc.8` · `0.1.1-rc.1` · `0.1.1-rc.2` · `0.1.2-rc.1` · `0.1.3-alpha.2` ·
33
+ `0.1.5-rc.1` · `0.1.5-rc.2` · `0.1.7-rc.1` · `0.1.7-rc.2` · `0.2.0-rc.2`
34
+
35
+ The list mirrors `dsh.compatibility.dshReleases` in `package.json`, and a unit test fails if
36
+ the two drift apart. `0.2.0-rc.2` is the DSH release DSH Desktop `0.2.0-rc.2` ships, so the
37
+ Desktop is covered by the same declaration; the app's help panel (the dialog's "?" button)
38
+ shows the same chips next to the plugin version. The plugin detects the DSH generation at
39
+ runtime and picks the matching API, and a release exposing neither fails loudly instead of
40
+ leaving an empty workspace. A release outside the list usually still works, but is
41
+ unverified.
42
+
27
43
  ## Usage
28
44
 
29
45
  Click the W button beside Settings at the sidebar foot to open the "Add WSL workspace" dialog. Pick a distribution from the list, then browse the directory tree or type an absolute Linux path (for example `/home/me/proj`) — use the Check button to verify the path exists before creating the workspace. The dialog follows the DeepSeek Harness UI language. The username field is optional: leave it empty to run commands as the distribution's default user, or name a Linux user of that distribution to run the session as that user instead (equivalent to `wsl.exe -u <username>`). The username only changes the bash tool's run identity — the file tools go through the Windows-side WSL share and are unaffected. Each workspace's username is kept in `<dshHome>/wsl-workspaces.json`; delete the entry (or recreate the workspace from the dialog) to return to the default user.
@@ -43,6 +59,90 @@ Click "Create & open" to start a new session in the workspace. In the new sessio
43
59
 
44
60
  ## Changelog
45
61
 
62
+ ### 0.7.5 — 2026-09-30
63
+
64
+ - **The persistent shell works on DSH Desktop again (issue #40).** The Desktop
65
+ host process *is* the packaged Electron executable running in node mode
66
+ (`ELECTRON_RUN_AS_NODE=1`; the Desktop's own code is
67
+ `new DesktopHostProcess(resources.node, …)` with
68
+ `resources.node = process.execPath`), and the plugin pointed the PTY backend's
69
+ `shellPath` at `process.execPath` with `shellArgs[0] = lib/wsl-relay.js`. An
70
+ Electron binary writes **nothing at all** under a ConPTY: the relay's
71
+ `wsl.exe` child inherits a dead stream, the relay exits 0 with zero bytes, the
72
+ backend's readiness probe never sees a prompt, and **every** `bash` call fails
73
+ with `PTY shell exited during startup`. The `0.1.0-rc.7` one-shot fallback is
74
+ unaffected, which is exactly what hid the failure behind a mode that works.
75
+ Measured with the host's own node-pty: same relay, same `\\wsl.localhost\…`
76
+ cwd, same environment — a real node gives a bash prompt, the Electron binary
77
+ gives 0 bytes and a clean exit.
78
+ - **The relay's interpreter is now resolved deliberately**
79
+ (`src/shared/relay-node.ts`): first the runtime payload the Desktop passes to
80
+ its host process (the `…/resources/runtime/primary-runtime` path in
81
+ `process.argv`, which carries a real node), then the same payload beside the
82
+ running executable (inside `Contents/Resources/…` on macOS), then
83
+ `DSH_DESKTOP_NODE_EXECUTABLE`, then a `node` on `PATH`, and only then
84
+ `process.execPath`. Every candidate is **asked what it is** (`-e` printing
85
+ `process.versions.electron`), because `--version` cannot tell them apart: with
86
+ `ELECTRON_RUN_AS_NODE` inherited the Electron executable answers with the
87
+ *node* version (measured: `v24.18.1`), so any `^v\d+\.\d+\.\d+` test selects
88
+ the broken one. A host that is not Electron (`dsh web`) keeps
89
+ `process.execPath` and spawns no probe at all, so its behaviour is identical
90
+ to 0.7.4; on the Desktop the chosen interpreter and every rejected candidate
91
+ go to the boot log.
92
+ - PR #39 diagnosed this correctly and pointed the same way, but its first
93
+ candidate, `DSH_DESKTOP_NODE_EXECUTABLE`, **is the Electron executable** — the
94
+ Desktop's own `resources/runtime/bin/node.cmd` is `set
95
+ ELECTRON_RUN_AS_NODE=1` followed by that variable — and its `--version` check
96
+ cannot tell the two apart. This fix takes the same direction with the payload
97
+ path first and a discriminator that actually rejects Electron.
98
+ - `tests/relay-node.test.mjs`: candidate derivation (argv / beside the
99
+ executable / the environment variable, including de-duplication and order) and
100
+ the Electron discriminator, including the version-shaped output that must be
101
+ refused.
102
+ - `scripts/compatibility/conpty-relay.mjs`: **a new standing gate** — it drives
103
+ the relay through a real ConPTY with the host's own node-pty and requires the
104
+ resolved interpreter to produce a live bash prompt. That is the invariant
105
+ issue #40 broke, and nothing checked it before. Registered in
106
+ `Run-Checks.ps1` and the harness, so it runs on every declared release.
107
+ - **The second error in issue #40 is fixed too**: `bash_background` handed the jobs
108
+ registry an `owner` of the wrong shape, which failed with
109
+ `session "[object Object]" has no live agent` on `0.1.7` and later. That contract
110
+ **changed at `0.1.7-rc.1`**: `start()` took the agent object before (the registry
111
+ reads `owner.id` and `owner.ctx`) and takes the session id after (resolving it with
112
+ `agents.get(id)`) — the host's own producers moved the same way, from
113
+ `owner: parent` to `owner: parent.id`. The plugin now picks the shape from whether
114
+ the registry offers `resolveOwner`; `tests/wsl-jobs.test.ts`, which had pinned the
115
+ wrong half as correct behaviour, now pins both contracts, and all eleven declared
116
+ releases were exercised one by one.
117
+ - `dsh.compatibility.dshReleases` declares `0.2.0-rc.2` (the DSH release DSH
118
+ Desktop 0.2.0-rc.2 ships), so this build claims eleven releases instead of ten.
119
+ - **The second error in issue #40 is fixed too**: `bash_background` handed the
120
+ jobs registry the agent object as the job owner, but the registry resolves that
121
+ owner with `ctx.agents.get(owner)` and wants the **session id**, so every call
122
+ failed with `session "[object Object]" has no live agent`. The host's own
123
+ producers pass `agent.id`. `tests/wsl-jobs.test.ts` had asserted the broken
124
+ contract as if it were correct; it now asserts the session id.
125
+ - **Verification**: sixteen harness checks on each of the eleven declared
126
+ releases (14/16 everywhere — the two failures are the documented baseline:
127
+ `typecheck`, and `host-api`, which needs a *running* frontend and passes 12/12
128
+ when pointed at one), plus the runbook's six-item frontend pass on all eleven
129
+ (dialog distro list, create & open, write, read, one-shot/persistent bash,
130
+ skills, reopen from the UI) with `web.err` at 0 bytes and every file re-read
131
+ independently on the Linux side.
132
+ - **And a full pass inside the real DSH Desktop.** The official installer was
133
+ unpacked and the Electron application itself was started (isolated `DSH_HOME`,
134
+ window driven over CDP, plugin installed and enabled through the Desktop's own
135
+ plugin panel). With **0.7.4** installed, every `bash` call returned
136
+ `PTY shell exited during startup` and `bash_background` returned
137
+ `session "[object Object]" has no live agent`, while the dialog, the
138
+ distribution list and the skills all worked. With **0.7.5**, bash returned
139
+ `6.18.33.2-microsoft-standard-WSL2` / `/home/mille/fx-3381` / `mille`, a second
140
+ independent `pwd` still said `/tmp`, `bash_background` returned `bash-1` with
141
+ `job_list` reporting `bash-1 [bash] running` and `bg.txt` really containing
142
+ `BG_3381_OK`, and the workspace still opened after a window reload. The boot log
143
+ names the interpreter it chose. The whole run is written up in
144
+ `docs/compatibility-evidence.md`.
145
+
46
146
  ### 0.7.4 — 2026-09-28
47
147
 
48
148
  - **The dialog works in DSH Desktop again (issues #35, #36).** The Desktop host
@@ -225,17 +325,17 @@ live skill catalog, a stateful shell, and tracked background jobs.
225
325
 
226
326
  ### 0.4.5 — 2026-09-19
227
327
 
228
- - **A project linked into a WSL workspace is discoverable now**: the `\\wsl.localhost` 9P share lists a Linux symlink but cannot resolve its target, so the skill scan — which already followed directory links on substrates that resolve them — skipped every linked-in project, and with it every nested project below it (the layout issue [#10](https://github.com/6Mikao9/dsh-wsl-workspace/issues/10) describes). When the share reports a link it cannot follow, the provider now asks the distribution itself (`wsl.exe -d <distro> -- readlink -f <linux path>`) and continues the walk at the real path. The fallback is bounded on purpose: at most 32 links per lookup, four calls in flight, a 10 s timeout each, and the existing depth / visited-directory / skill-directory budgets are untouched. Because the walk continues at the resolved path, a project reachable both directly and through a link is visited once, and a link that points back at the workspace root is absorbed by the visited set instead of looping.
328
+ - **A project linked into a WSL workspace is discoverable now**: the `\\wsl.localhost` 9P share lists a Linux symlink but cannot resolve its target, so the skill scan — which already followed directory links on substrates that resolve them — skipped every linked-in project, and with it every nested project below it (the layout issue [#10](https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace/issues/10) describes). When the share reports a link it cannot follow, the provider now asks the distribution itself (`wsl.exe -d <distro> -- readlink -f <linux path>`) and continues the walk at the real path. The fallback is bounded on purpose: at most 32 links per lookup, four calls in flight, a 10 s timeout each, and the existing depth / visited-directory / skill-directory budgets are untouched. Because the walk continues at the resolved path, a project reachable both directly and through a link is visited once, and a link that points back at the workspace root is absorbed by the visited set instead of looping.
229
329
  - **What the fallback does not cover**: `read/write/edit` still resolve their paths through `WslFileSystem`, which does not follow Linux links, so reading or writing a link path reports it missing — use the real path. The help panel's known-issues list now states that instead of promising a fallback "not implemented yet".
230
330
  - **Why one `wsl.exe` per link** (measured, and worth recording): `wsl.exe` silently drops the arguments that follow a command (`sh -c 'echo $#' sh a b c` answers 0), and its command-line parser truncates an argument containing a double quote, so a batched `sh` loop cannot be made reliable through it. A bare `readlink -f a b c` is no better: GNU `readlink` stops at the first path it cannot resolve and still exits non-zero, which would silently starve the rest of the batch. Passing each path as a process argument to one short call avoids quoting entirely — paths with spaces, quotes and backslashes all resolve — at the cost of one process per link (about 35 ms warm; six links cost 179 ms end to end on this machine, and a workspace with no links never starts a distribution process at all).
231
331
  - **Verification**: the eight declared releases (`0.1.0-rc.7` … `0.1.5-rc.2`) pass the same 8/10 harness checks as 0.4.4 — only the documented `typecheck` baseline and the check that needs a live server fail. The real-9P check now builds a fixture whose only path in is a symlink and asserts the linked project, its nested project and its service through `get()`; the same walk with the fallback face removed finds neither, which is the pre-fix behaviour reproduced in the same run. On a live WSL fixture (`/home/mille/symprobe/ws`: a link out of the workspace, a link chain, a file link, a dangling link and a loop back to the root) the catalog went from 2 skills to 5, and `get()` read every body through the resolved locator.
232
332
 
233
333
  ### 0.4.4 — 2026-09-19
234
334
 
235
- - **A preset built on top of a WSL variant could not be used at all**: this generator recognises its own output by id prefix (`wsl-`), so a user preset that started life as a copy of `wsl-standard` or `wsl-cordis` — a "data mode" that carries its own world, say — was treated as a plain source preset and had a *second* world group appended to it. DSH refuses a composition carrying two `wsl-world` rows, so choosing that mode failed outright with `无法切换到「WSL · <name>」:duplicate loader entry id: wsl-world`; on a release that mounts the group before validating row ids the same duplication surfaces one step later as `tool "str replace editor" is already registered in this scope` (the report in [#24](https://github.com/6Mikao9/dsh-wsl-workspace/pull/24)). The generator now replaces the world group it finds — identified by the mounted `shell-wsl`/`fs-wsl` provider ids, so a copy whose group was renamed is caught too — and every variant ends up with exactly one world pointing at this installation's providers. A top-level row id that appears twice in a source is reduced to its first occurrence as well, because DSH rejects the whole preset on a duplicate id rather than the offending row.
236
- - **`tool-str-replace-editor` rows are replaced like the older `str-replace-editor` row** ([#24](https://github.com/6Mikao9/dsh-wsl-workspace/pull/24)): newer rosters name the editor row that way, and it registers the same `str_replace_editor` tool as the world group's own editor row, so the source row is dropped just like its predecessor and the WSL-aware editor the variant injects stays.
335
+ - **A preset built on top of a WSL variant could not be used at all**: this generator recognises its own output by id prefix (`wsl-`), so a user preset that started life as a copy of `wsl-standard` or `wsl-cordis` — a "data mode" that carries its own world, say — was treated as a plain source preset and had a *second* world group appended to it. DSH refuses a composition carrying two `wsl-world` rows, so choosing that mode failed outright with `无法切换到「WSL · <name>」:duplicate loader entry id: wsl-world`; on a release that mounts the group before validating row ids the same duplication surfaces one step later as `tool "str replace editor" is already registered in this scope` (the report in [#24](https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace/pull/24)). The generator now replaces the world group it finds — identified by the mounted `shell-wsl`/`fs-wsl` provider ids, so a copy whose group was renamed is caught too — and every variant ends up with exactly one world pointing at this installation's providers. A top-level row id that appears twice in a source is reduced to its first occurrence as well, because DSH rejects the whole preset on a duplicate id rather than the offending row.
336
+ - **`tool-str-replace-editor` rows are replaced like the older `str-replace-editor` row** ([#24](https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace/pull/24)): newer rosters name the editor row that way, and it registers the same `str_replace_editor` tool as the world group's own editor row, so the source row is dropped just like its predecessor and the WSL-aware editor the variant injects stays.
237
337
  - **Variant display names are no longer double-quoted**: the variant's `preset.yml` copied the source's `name:` scalar verbatim, so a quoted `name: 'Data mode'` reached the mode picker as `WSL · ''Data mode''`. The scalar is unquoted before it is re-emitted.
238
- - **Not adopted from [#24](https://github.com/6Mikao9/dsh-wsl-workspace/pull/24)**: disabling the `tool-cordis` row to avoid a duplicate inspect-provider registration. A `disabled` row never applies, so the WSL variant of Creator mode silently lost `cordis_inspect_list` / `cordis_inspect_query` (checked against 0.4.3, where both are present and answer with the host and the client providers); the PR's own description that the model "can still see the tools in the catalog" is not what happens. The registration their report shows needs that row applied twice, which the row-id reduction above now prevents where a copied preset caused it.
338
+ - **Not adopted from [#24](https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace/pull/24)**: disabling the `tool-cordis` row to avoid a duplicate inspect-provider registration. A `disabled` row never applies, so the WSL variant of Creator mode silently lost `cordis_inspect_list` / `cordis_inspect_query` (checked against 0.4.3, where both are present and answer with the host and the client providers); the PR's own description that the model "can still see the tools in the catalog" is not what happens. The registration their report shows needs that row applied twice, which the row-id reduction above now prevents where a copied preset caused it.
239
339
  - **Help panel tidied up**: the panel now opens with a greeting line and the repository link, carries a "What's new" section for this build, and lists only the limitations that still apply — the historical "fixed in 0.4.3" note and the per-generation API walkthrough are gone. The compatibility chips are untouched: they are the manifest this build declares, not history.
240
340
  - **Verification**: the eight declared releases (`0.1.0-rc.7` … `0.1.5-rc.2`) pass the same 8/10 harness checks as 0.4.3 — only the documented `typecheck` baseline and the check that needs a live server fail; 136 transforms over every shipped preset of the 17 installed runtimes are unchanged apart from the repair, and all 68 "copied variant" cases resolve to a single fresh world group.
241
341
  - **Per-mode matrix with a real model** (every WSL variant, not just the default one): on `0.1.0-rc.7`, `0.1.1-rc.2`, `0.1.3-alpha.2` and `0.1.5-rc.2` each of the four variants — Standard, PTC, Minimal, Creator — was driven through the browser and asked to write a file with its file tool, run `uname -r; pwd; whoami` in bash and land that output in the workspace, then read the file back. Every mode produced `MODE-<mode>-OK` and a WSL2 kernel line in `/home/mille/<workspace>/notes/` with no loader error; the follow-up bash call lands in the workspace again, which is the documented per-call shell (the PTY group stays dropped). `0.1.2-rc.1` and `0.1.5-rc.1` were driven through all four modes without the file/bash assertions.
@@ -243,7 +343,7 @@ live skill catalog, a stateful shell, and tracked background jobs.
243
343
 
244
344
  ### 0.4.3 — 2026-09-11
245
345
 
246
- - **The persona text moved in `0.1.3-alpha.2`** ([#22](https://github.com/6Mikao9/dsh-wsl-workspace/issues/22)): DSH renamed the persona's model-facing scalar from `text` to an inline `suffix` plus a folded `prefix`, and the variant generator only recognised `text: >-`. On that line the WSL environment sentence was never appended - the session still ran inside the distribution, but the model was never told that its working directory is a Linux path reachable from Windows as `/mnt/<drive>`. The generator now amends `suffix`, `text` or `prefix` (folding an inline scalar into a block scalar when needed, so the sentence joins the working-directory line exactly where the legacy `text` block put it), and a persona carrying `complete: true` is still left alone. Verified on seven releases: the five older ones keep their persona block byte-identical, and the two newer ones now carry the sentence into the model's system message.
346
+ - **The persona text moved in `0.1.3-alpha.2`** ([#22](https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace/issues/22)): DSH renamed the persona's model-facing scalar from `text` to an inline `suffix` plus a folded `prefix`, and the variant generator only recognised `text: >-`. On that line the WSL environment sentence was never appended - the session still ran inside the distribution, but the model was never told that its working directory is a Linux path reachable from Windows as `/mnt/<drive>`. The generator now amends `suffix`, `text` or `prefix` (folding an inline scalar into a block scalar when needed, so the sentence joins the working-directory line exactly where the legacy `text` block put it), and a persona carrying `complete: true` is still left alone. Verified on seven releases: the five older ones keep their persona block byte-identical, and the two newer ones now carry the sentence into the model's system message.
247
347
  - **Help panel**: the dialog gained a "?" button that opens an in-place panel - the DSH releases this build declares (read from `package.json` through the host route, so the list can never drift from the manifest), how the plugin is used, its features, and the limitations it cannot fix.
248
348
  - **The skill catalog now reaches UNC workspaces**: the host skill provider watches a workspace through `chokidar`, and watching a `\\wsl.localhost\...` path fails; the failed watcher makes the skill snapshot report `complete: false`, and `dsh-tool-skill` withholds the *entire* catalog message while a snapshot is incomplete — so a WSL session's model saw no skills at all, not even the ones the plugin had discovered. The variant generator now pins `watch: false` on the `skill-filesystem` row (merged into an existing `config:` block when there is one, and left alone when the source declares `watch` itself), which makes the host collect the catalog once at session start instead. Verified end to end on `0.1.5-rc.2`: the model's context carries the `<available_skills>` list. Trade-off: a skill added mid-session appears in the next session rather than the running one; skill bodies are still read live.
249
349
  - **`verify-lib` hardening**: its comment/string stripper could pair a lone apostrophe inside a comment with a later one and swallow the rest of the bundle, which made every `node:*` import look tree-shaken. The quote rules now stop at a newline, exactly as a JavaScript string does.
@@ -273,7 +373,7 @@ live skill catalog, a stateful shell, and tracked background jobs.
273
373
 
274
374
  ### 0.4.0 — 2026-08-29
275
375
 
276
- Follow-ups from the [#12](https://github.com/6Mikao9/dsh-wsl-workspace/issues/12) limitation list and the [#13](https://github.com/6Mikao9/dsh-wsl-workspace/issues/13) compatibility work:
376
+ Follow-ups from the [#12](https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace/issues/12) limitation list and the [#13](https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace/issues/13) compatibility work:
277
377
 
278
378
  - **Lookup cache**: completed skill-catalog lookups are cached per scan root for 10 seconds, so repeated catalog builds no longer rescan the workspace over the slow 9P share; `get()` keeps reading skill bodies live, and freshly added skills appear within the TTL window.
279
379
  - **Symlinked projects — investigated in 0.4.0, resolved in 0.4.5**: the discovery walk now recognizes directory symlinks explicitly and prunes them safely (no crashes, no loops), and the probe showed that following them is impossible over the `\\wsl.localhost` share itself (the Windows side cannot resolve Linux symlink targets: `readlink` → `EISDIR`, `stat`/`readdir` → `ENOENT`); 0.4.5 resolves them through the distribution instead, so linked-in projects are discoverable (see that changelog entry). A name+body fingerprint dedupe also guarantees aliased skill files can never publish twice on substrates that do resolve links.
@@ -283,7 +383,7 @@ Follow-ups from the [#12](https://github.com/6Mikao9/dsh-wsl-workspace/issues/12
283
383
 
284
384
  ### 0.3.2 — 2026-08-29
285
385
 
286
- - **WSL workspace sessions now inject nested-project skill catalogs** ([#10](https://github.com/6Mikao9/dsh-wsl-workspace/issues/10)): `.dsh/skills` and `.agents/skills` directories of projects nested below the registered workspace root are discovered and published with the host's project ranks and sources, so the model sees the same skill catalog it would see when the session cwd is the project folder itself. Discovery is depth- and budget-bounded, prunes `node_modules`/dot-directories, and leaves non-WSL sessions untouched.
386
+ - **WSL workspace sessions now inject nested-project skill catalogs** ([#10](https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace/issues/10)): `.dsh/skills` and `.agents/skills` directories of projects nested below the registered workspace root are discovered and published with the host's project ranks and sources, so the model sees the same skill catalog it would see when the session cwd is the project folder itself. Discovery is depth- and budget-bounded, prunes `node_modules`/dot-directories, and leaves non-WSL sessions untouched.
287
387
  - **Host-parity scan root**: lookups from inside a project subtree resolve the nearest `.git` ancestor first, so the enclosing project's skills stay visible from deeper cwds; skills above that ancestor do not leak.
288
388
  - **Hardening**: the skill-root budget is enforced per push, and the `skills.registerProvider` call is guarded so a host whose `skills` service has a different shape can no longer break plugin load.
289
389
  - **Housekeeping**: removed stale prebuilt `lib/` chunks that shipped dead vendor code (including an inlined schemastery copy that triggered dsh.so's `new Function` static rule); added `scripts/repro-setup.sh` plus a nested skill-catalog regression suite, and a matching TESTING.md section.
package/README.pt.md CHANGED
@@ -8,6 +8,8 @@
8
8
  ![alt text](image-3.png)
9
9
  Adicione um espaço de trabalho WSL a partir da interface web do DeepSeek Harness e execute toda a sessão do agente — comandos bash e leitura/escrita de arquivos — dentro de uma distribuição WSL local com caminhos em formato Linux. Não é necessário instalar nada dentro do WSL. A sessão pode acessar WSL e Windows ao mesmo tempo: os comandos bash são executados dentro da distribuição WSL, enquanto os arquivos do Windows continuam acessíveis via `/mnt/<unidade>` (por exemplo `/mnt/c/Users/...`).
10
10
 
11
+ A lista de versões do DSH suportadas está na secção Compatibility do [README.md](README.md).
12
+
11
13
  ## Instalação
12
14
 
13
15
  Escolha um dos três métodos abaixo e reinicie o `dsh web`:
@@ -17,7 +19,7 @@ Escolha um dos três métodos abaixo e reinicie o `dsh web`:
17
19
  dsh plugin --profile web add dsh-wsl-workspace
18
20
 
19
21
  # 2) Repositório GitHub (inclui lib/ pré-compilado, sem build local)
20
- dsh plugin --profile web add https://github.com/6Mikao9/dsh-wsl-workspace
22
+ dsh plugin --profile web add https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace
21
23
 
22
24
  # 3) Diretório local (desenvolvimento / uso próprio)
23
25
  dsh plugin --profile web add D:\path\to\dsh-wsl-workspace
package/README.ru.md CHANGED
@@ -8,6 +8,8 @@
8
8
  ![alt text](image-3.png)
9
9
  Добавьте рабочее пространство WSL из веб-интерфейса DeepSeek Harness и запустите всю сессию агента — команды bash и чтение/запись файлов — внутри локального дистрибутива WSL с путями в формате Linux. Ничего не нужно устанавливать внутри WSL. Сессия может одновременно обращаться и к WSL, и к Windows: команды bash выполняются внутри дистрибутива WSL, а файлы Windows остаются доступными через `/mnt/<диск>` (например `/mnt/c/Users/...`).
10
10
 
11
+ Список поддерживаемых версий DSH — в разделе Compatibility файла [README.md](README.md).
12
+
11
13
  ## Установка
12
14
 
13
15
  Выберите один из трёх способов ниже, затем перезапустите `dsh web`:
@@ -17,7 +19,7 @@
17
19
  dsh plugin --profile web add dsh-wsl-workspace
18
20
 
19
21
  # 2) Репозиторий GitHub (включает предсобранный lib/, локальная сборка не нужна)
20
- dsh plugin --profile web add https://github.com/6Mikao9/dsh-wsl-workspace
22
+ dsh plugin --profile web add https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace
21
23
 
22
24
  # 3) Локальная папка (разработка / личное использование)
23
25
  dsh plugin --profile web add D:\path\to\dsh-wsl-workspace
package/README.zh.md CHANGED
@@ -16,7 +16,7 @@
16
16
  dsh plugin --profile web add dsh-wsl-workspace
17
17
 
18
18
  # 2) GitHub 仓库(仓库内已含预构建 lib/,无需本地构建)
19
- dsh plugin --profile web add https://github.com/6Mikao9/dsh-wsl-workspace
19
+ dsh plugin --profile web add https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace
20
20
 
21
21
  # 3) 本地目录(开发/自用)
22
22
  dsh plugin --profile web add D:\path\to\dsh-wsl-workspace
@@ -24,6 +24,17 @@ dsh plugin --profile web add D:\path\to\dsh-wsl-workspace
24
24
 
25
25
  重启 `dsh web` 后,侧栏底部 Settings 旁出现 W 按钮。
26
26
 
27
+ ## 兼容性
28
+
29
+ 这份构建声明兼容下列 DSH 版本,每一条都在隔离实例上实测过(独立 `DSH_HOME`、依赖固定到该版本、跑满整套门禁):
30
+
31
+ `0.1.0-rc.7` · `0.1.0-rc.8` · `0.1.1-rc.1` · `0.1.1-rc.2` · `0.1.2-rc.1` · `0.1.3-alpha.2` ·
32
+ `0.1.5-rc.1` · `0.1.5-rc.2` · `0.1.7-rc.1` · `0.1.7-rc.2` · `0.2.0-rc.2`
33
+
34
+ 这份列表与 `package.json` 里的 `dsh.compatibility.dshReleases` 一一对应,单测会在两者不一致时失败。
35
+ 其中 `0.2.0-rc.2` 就是 DSH Desktop `0.2.0-rc.2` 自带的 DSH 版本,所以桌面版由同一份声明覆盖;应用内帮助面板(对话框右上角「?」)会显示同一组 chip 以及插件版本。
36
+ 插件在运行时自动识别 DSH 版本并选用对应的 API;两边都不支持时会明确报错,而不是留下一个空工作区。版本不在列表里通常仍然可用,但未经验证。
37
+
27
38
  ## 使用
28
39
  点侧栏底部 Settings 旁的 W 按钮,打开「添加 WSL 工作区」对话框。先从下拉框选择一个发行版,再浏览目录树或直接输入 Linux 绝对路径(如 `/home/me/proj`),可以点「检查」确认路径存在。对话框文案跟随 DSH 界面语言。用户名是可选项:留空则以该发行版的默认用户运行,填写该发行版里的某个 Linux 用户名则以该用户运行(等价于 `wsl.exe -u <用户名>`)。用户名只影响 bash 命令的运行身份,文件工具通过 Windows 侧的 WSL 共享访问、不受其影响;每个工作区填写的用户名保存在 `<dshHome>/wsl-workspaces.json`,删除对应条目(或重开对话框重建工作区)即可恢复默认用户。
29
40
 
@@ -44,6 +55,19 @@ dsh plugin --profile web add D:\path\to\dsh-wsl-workspace
44
55
 
45
56
  英文完整历史见 [README.md](README.md),本节为对应中文记录(0.4.3 及更早为摘要)。
46
57
 
58
+ ### 0.7.5 — 2026-09-30
59
+
60
+ - **DSH Desktop 上持久 shell 又能用了(issue #40)**。Desktop 的宿主进程**本身就是**打包后的 Electron 可执行文件、以 node 模式运行(`ELECTRON_RUN_AS_NODE=1`;Desktop 自己的代码是 `new DesktopHostProcess(resources.node, …)`,而 `resources.node = process.execPath`),插件却把 PTY 后端的 `shellPath` 指向 `process.execPath`、`shellArgs[0]` 指向 `lib/wsl-relay.js`。Electron 二进制在 **ConPTY** 下不输出任何字节:中继的 `wsl.exe` 子进程继承到一条死流,中继 0 字节退出 0,后端的就绪探测永远看不到提示符,于是**每一次** `bash` 都以 `PTY shell exited during startup` 失败;而 `0.1.0-rc.7` 那条一次性 bash 回退路径不受影响,故障正好被一个能用的模式盖住了。用宿主自己的 node-pty 实测:同一份中继、同一个 `\\wsl.localhost\…` cwd、同一份环境,真 node 给出 bash 提示符,Electron 是 0 字节并干净退出。
61
+ - **中继的解释器改为显式解析**(`src/shared/relay-node.ts`):先取 Desktop 传给宿主进程的 runtime payload 路径(`process.argv` 里的 `…/resources/runtime/primary-runtime`,那份 payload 里带着它自带的真 node),再退到可执行文件旁的同一路径(macOS 包内是 `Contents/Resources/…`)、`DSH_DESKTOP_NODE_EXECUTABLE`、`PATH` 上的 `node`,最后才回到 `process.execPath`。每个候选都要被**问一次"你到底是什么"**(`-e` 打印 `process.versions.electron`):只看 `--version` 分不出来——继承 `ELECTRON_RUN_AS_NODE` 时 Electron 回答的是 node 版本号(实测 `v24.18.1`),任何 `^v\d+\.\d+\.\d+` 判断都会选中那个坏的。非 Electron 宿主(`dsh web`)直接返回 `process.execPath`、不启动任何探测进程,行为与 0.7.4 完全一致;Desktop 上则把选中的解释器与被拒绝的候选写进启动日志。
62
+ - 同方向的 PR #39 诊断是对的(根因确实是 Electron 解释器),但它的候选链第 1 项 `DSH_DESKTOP_NODE_EXECUTABLE` **指向的就是 Electron 可执行文件**——Desktop 自己的 `resources/runtime/bin/node.cmd` 就是 `set ELECTRON_RUN_AS_NODE=1` 后运行这个变量——而它的 `--version` 校验分不出两者。本修复采用同样的方向,但把 payload 路径放在最前,并用真正的判别器把 Electron 挡掉。
63
+ - `tests/relay-node.test.mjs`:候选推导(argv / 可执行文件旁 / 环境变量,含去重与排序)与"是不是 Electron"的判定,包括 `--version` 那种形似版本号、但必须被拒的输出。
64
+ - `scripts/compatibility/conpty-relay.mjs`:**新的常驻门禁**——在真实 ConPTY 下用宿主自己的 node-pty 跑中继,要求解析出的解释器给出活的 bash 提示符。这正是 #40 破坏的那条不变量,此前没有任何检查覆盖它。它注册进 `Run-Checks.ps1` 与 harness,每个已声明版本都跑。
65
+ - **顺带修掉了 issue #40 里的第二个报错**:`bash_background` 交给 jobs 注册表的 `owner` 形状不对,在 `0.1.7` 及以后每次都报 `session "[object Object]" has no live agent`。实测这条契约**在 `0.1.7-rc.1` 变过**:之前 `start()` 收 agent 对象本身(注册表读 `owner.id` 与 `owner.ctx`),之后收**会话 id**(用 `agents.get(id)` 解析)——宿主自己的生产者也是同时从 `owner: parent` 改成 `owner: parent.id`。现在按注册表是否提供 `resolveOwner` 选择该传哪种形状;`tests/wsl-jobs.test.ts` 原本把错误的那一半当成正确行为断言了下来,现在两种契约都钉住了,十一个已声明版本也逐一实测过。
66
+ - `dsh.compatibility.dshReleases` 增加 `0.2.0-rc.2`(DSH Desktop 0.2.0-rc.2 自带的 DSH 版本),这份构建声明的版本从十个变成十一个;README 也新增「兼容性」一节列出这十一个版本,并有单测保证它和 `package.json` 的声明不会各说各话。
67
+ - **顺带修掉了 issue #40 里的第二个报错**:`bash_background` 把 agent 对象当成 job 的 owner 交给 jobs 注册表,而注册表要的是**会话 id**——它用 `ctx.agents.get(owner)` 去找活着的 agent,拿到对象必然找不到,于是每次都报 `session "[object Object]" has no live agent`(宿主自己的生产者传的是 `agent.id`)。`tests/wsl-jobs.test.ts` 原本把这个错误契约当成正确行为断言了下来,现在改成断言会话 id。
68
+ - **验证**:十一个已声明版本各跑十六项 harness 检查(一律 14/16,两项失败是既有基线:`typecheck`,以及需要**运行中前端**的 `host-api`——对着真实实例单独跑是 12/12);十一个版本各跑一遍 runbook 的六项前端验收(发行版下拉、创建并打开、写、读、一次性/持久 bash、skills、前端重开),`web.err` 全为 0 字节,文件在 Linux 侧独立复核。
69
+ - **另外在真的 DSH Desktop 里跑了完整一遍**(把官方安装包解包后直接启动那个 Electron 应用,用 CDP 驱动窗口,并走桌面版自己的插件面板安装/启用插件,`DSH_HOME` 指向隔离目录):装 **0.7.4** 时 `bash` 每次都报 `PTY shell exited during startup`、`bash_background` 报 `session "[object Object]" has no live agent`,而对话框、发行版列表、技能都正常;换成 **0.7.5** 后 `bash` 返回 `6.18.33.2-microsoft-standard-WSL2` / `/home/mille/fx-3381` / `mille`,第二次独立 `pwd` 仍是 `/tmp`(持久性成立),`bash_background` 返回 `bash-1`、`job_list` 报 `bash-1 [bash] running`、`bg.txt` 里确实是 `BG_3381_OK`,刷新窗口后工作区仍能重开;启动日志里是 `persistent shell: relay interpreter is …\runtime\primary-runtime\dependencies\node\bin\node.exe — node 24.21.0 from the runtime payload named in argv`。完整过程记在 `docs/compatibility-evidence.md`。
70
+
47
71
  ### 0.7.4 — 2026-09-28
48
72
 
49
73
  - **在 DSH Desktop 上对话框又能用了(issue #35、#36)**。Desktop 宿主会在插件加载**之前**包装 `child_process`:它装上普通的 `exec`/`execFile` 包装函数,再用 `syncBuiltinESMExports()` 把它们回写到内建模块。这份 `execFile` 上没有 `util.promisify.custom`,于是 `promisify(execFile)` 退回通用实现——它只用回调的**第一个**参数 resolve,也就是 stdout 字符串——结果每次调用拿到的 `result.stdout` 都是 `undefined`。发行版查询去读 `.stdout`,抛出 `Cannot read properties of undefined (reading 'includes')`,前端把它吞掉、渲染出一个空的发行版列表:对话框能打开、下拉框里却一个发行版都没有,「创建并打开」走不完;而同一台机器上 `wsl.exe -l -q` 在终端里列得好好的。三处调用现在都改用 `execFile` 的**回调**形式,任何包装都改不了这个签名。
@@ -100,24 +124,24 @@ WSL 世界现在在"会话能察觉到的每一处"都与宿主一致,最后
100
124
 
101
125
  ### 0.4.5 — 2026-09-19
102
126
 
103
- - **链进 WSL 工作区的项目现在能被发现了**:`\\wsl.localhost` 9P 共享会把 Linux 符号链接当作条目列出来,却解析不了它的目标,于是技能扫描(本来就会在能解析链接的底层上跟随目录链接)直接跳过所有链接进来的项目,连带跳过它下面的嵌套项目(即 [#10](https://github.com/6Mikao9/dsh-wsl-workspace/issues/10) 描述的那种布局)。现在只要共享报出一个它跟随不了的链接,插件就回头问发行版本身(`wsl.exe -d <发行版> -- readlink -f <Linux 路径>`),拿到真实路径后从那里继续走。这条回退是有意加了上限的:每次查找最多 32 条链接、最多 4 个调用并发、单次超时 10 秒,原有的深度 / 已访问目录 / 技能目录预算不变。由于继续扫描的位置是解析后的真实路径,同一项目既被直接访问又被链接访问时只会走一次,指回工作区根目录的链接环也会被已访问集合吸收,不会打转。
127
+ - **链进 WSL 工作区的项目现在能被发现了**:`\\wsl.localhost` 9P 共享会把 Linux 符号链接当作条目列出来,却解析不了它的目标,于是技能扫描(本来就会在能解析链接的底层上跟随目录链接)直接跳过所有链接进来的项目,连带跳过它下面的嵌套项目(即 [#10](https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace/issues/10) 描述的那种布局)。现在只要共享报出一个它跟随不了的链接,插件就回头问发行版本身(`wsl.exe -d <发行版> -- readlink -f <Linux 路径>`),拿到真实路径后从那里继续走。这条回退是有意加了上限的:每次查找最多 32 条链接、最多 4 个调用并发、单次超时 10 秒,原有的深度 / 已访问目录 / 技能目录预算不变。由于继续扫描的位置是解析后的真实路径,同一项目既被直接访问又被链接访问时只会走一次,指回工作区根目录的链接环也会被已访问集合吸收,不会打转。
104
128
  - **这条回退不覆盖什么**:`read/write/edit` 仍然走 `WslFileSystem` 的路径解析,它不跟随 Linux 链接,因此直接读写链接路径会报路径不存在——请用真实路径。帮助面板的「已知问题」现在如实写这一点,而不再是"可以用 readlink 补,但尚未实现"。
105
129
  - **为什么一条链接一个 `wsl.exe`**(实测记录):`wsl.exe` 会**丢掉**命令之后的参数(`sh -c 'echo $#' sh a b c` 返回 0),而且它的命令行解析会把含双引号的参数截断,所以批量的 `sh` 循环没法可靠地透过它工作。直接 `readlink -f a b c` 也不行:GNU `readlink` 遇到第一个解析不了的路径就停下(仍以非零退出),批次里后面的链接会被无声地饿死。把每个路径作为进程参数交给一次短调用,就完全绕开了引号问题——带空格、引号、反斜杠的路径都能解析——代价是每条链接一个进程(热态约 35 ms;本机上 6 条链接端到端 179 ms;没有链接的工作区则完全不会启动发行版进程)。
106
130
  - **验证**:八个已声明版本(`0.1.0-rc.7` … `0.1.5-rc.2`)跑通与 0.4.4 相同的 8/10 项检查(仅剩既有的 `typecheck` 基线与一项需要在线服务的检查)。真实 9P 检查现在会构造"只能靠符号链接进入"的 fixture,断言链接进来的项目、它下面的嵌套项目以及 `get()` 取正文;同一次运行里把回退能力摘掉再走一遍,两者都找不到,即修复前的行为在原位复现。在真实 WSL fixture 上(`/home/mille/symprobe/ws`:链到工作区外的目录、链接链、指向文件的链接、悬空链接、指回根目录的环)技能目录从 2 个变成 5 个,`get()` 也都能通过解析后的定位读回正文。
107
131
 
108
132
  ### 0.4.4 — 2026-09-19
109
133
 
110
- - **在 WSL 变体基础上改出来的自定义模式完全用不了**:本插件靠 id 前缀(`wsl-`)识别自己的产物,于是「把生成的 `wsl-standard` / `wsl-cordis` 复制改名再改」得到的用户预设会被当成普通源预设,被**再追加一个世界组**。DSH 拒绝含两个 `wsl-world` 行的组合,选中该模式时直接失败:`无法切换到「WSL · <名称>」:duplicate loader entry id: wsl-world`;在"先挂载组、后校验行 id"的版本上,同一处重复会晚一步表现为 `tool "str replace editor" is already registered in this scope`(即 [#24](https://github.com/6Mikao9/dsh-wsl-workspace/pull/24) 报告的现象)。现在生成器会**替换**它找到的世界组(按挂载的 `shell-wsl` / `fs-wsl` provider id 识别,改过组名也能认出),每个变体最终只挂一个世界,且指向本机安装的 provider。源里重复出现的顶层行 id 也只保留第一处——DSH 遇到重复 id 是**整个预设**不可用,而不是只丢那一行。
111
- - **`tool-str-replace-editor` 行与旧的 `str-replace-editor` 行一样被替换**([#24](https://github.com/6Mikao9/dsh-wsl-workspace/pull/24)):较新的名单用这个 id,而它注册的工具名与注入世界组里那个编辑器行相同,所以源里的那行会被丢弃,变体注入的、走 WSL 文件系统的编辑器保留。
134
+ - **在 WSL 变体基础上改出来的自定义模式完全用不了**:本插件靠 id 前缀(`wsl-`)识别自己的产物,于是「把生成的 `wsl-standard` / `wsl-cordis` 复制改名再改」得到的用户预设会被当成普通源预设,被**再追加一个世界组**。DSH 拒绝含两个 `wsl-world` 行的组合,选中该模式时直接失败:`无法切换到「WSL · <名称>」:duplicate loader entry id: wsl-world`;在"先挂载组、后校验行 id"的版本上,同一处重复会晚一步表现为 `tool "str replace editor" is already registered in this scope`(即 [#24](https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace/pull/24) 报告的现象)。现在生成器会**替换**它找到的世界组(按挂载的 `shell-wsl` / `fs-wsl` provider id 识别,改过组名也能认出),每个变体最终只挂一个世界,且指向本机安装的 provider。源里重复出现的顶层行 id 也只保留第一处——DSH 遇到重复 id 是**整个预设**不可用,而不是只丢那一行。
135
+ - **`tool-str-replace-editor` 行与旧的 `str-replace-editor` 行一样被替换**([#24](https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace/pull/24)):较新的名单用这个 id,而它注册的工具名与注入世界组里那个编辑器行相同,所以源里的那行会被丢弃,变体注入的、走 WSL 文件系统的编辑器保留。
112
136
  - **变体显示名不再多出一层引号**:变体的 `preset.yml` 原先逐字复制源里的 `name:` 标量,于是 `name: 'Data mode'` 到了模式选择器里变成 `WSL · ''Data mode''`;现在会先去掉一层 YAML 引号再写出。
113
- - **未采纳 [#24](https://github.com/6Mikao9/dsh-wsl-workspace/pull/24) 的做法**:禁用 `tool-cordis` 行来规避 inspect provider 重复注册。`disabled` 的行根本不会 apply,结果是 WSL 创造模式**直接丢掉** `cordis_inspect_list` / `cordis_inspect_query`(已与 0.4.3 对照:0.4.3 里两个工具都在,且能返回 host 与 client 两侧的 provider);PR 描述里"模型仍能在工具目录看到、只是不能用"与实际不符。其报告中的重复注册需要该行被 apply 两次,而这在"由复制预设引起"的情形下已由上面的行 id 去重解决。
137
+ - **未采纳 [#24](https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace/pull/24) 的做法**:禁用 `tool-cordis` 行来规避 inspect provider 重复注册。`disabled` 的行根本不会 apply,结果是 WSL 创造模式**直接丢掉** `cordis_inspect_list` / `cordis_inspect_query`(已与 0.4.3 对照:0.4.3 里两个工具都在,且能返回 host 与 client 两侧的 provider);PR 描述里"模型仍能在工具目录看到、只是不能用"与实际不符。其报告中的重复注册需要该行被 apply 两次,而这在"由复制预设引起"的情形下已由上面的行 id 去重解决。
114
138
  - **帮助面板整理**:面板最前面是一句问候语与仓库链接,新增「本次更新」一节,已知问题只保留仍然成立的条目——历史上的「0.4.3 已修复」说明与按版本讲旧 API 的段落已删除。兼容性 chips 保持原样:它们是本构建声明的清单,不是历史。
115
139
  - **逐模式矩阵(真模型)**:在 `0.1.0-rc.7`、`0.1.1-rc.2`、`0.1.3-alpha.2`、`0.1.5-rc.2` 上,四个 WSL 变体(标准 / PTC / 极简 / 创造)各自跑一遍:用文件工具写文件、用 bash 执行 `uname -r; pwd; whoami` 并把输出重定向落盘、再读回文件。每个模式都在 `/home/mille/<工作区>/notes/` 里留下了 `MODE-<模式>-OK` 与 WSL2 内核输出,零 loader 报错;随后单独一次 bash 调用又回到工作区目录,即文档所写的「按次 shell」(PTY 组仍然不注入)。`0.1.2-rc.1` 与 `0.1.5-rc.1` 只做了四模式切换与真实回合,没有文件/bash 断言。
116
140
  - **验证**:八个已声明版本(`0.1.0-rc.7` … `0.1.5-rc.2`)跑通与 0.4.3 相同的 8/10 项检查(仅剩既有的 `typecheck` 基线与一项需要在线服务的检查);17 个已安装运行时里全部 shipped 预设共 136 次变换,除本次修复外结果不变;68 个"复制变体"场景全部收敛为单一新世界组。另做浏览器 + 真模型验证:复制变体模式本身、创造模式(检查工具完整)以及 `0.1.0-rc.7` 的标准流程。
117
141
 
118
142
  ### 0.4.3 — 2026-09-11
119
143
 
120
- - **persona 文本位置变更**([#22](https://github.com/6Mikao9/dsh-wsl-workspace/issues/22)):DSH 把 persona 面向模型的字段从 `text` 改为内联 `suffix` + 折叠 `prefix`,而变体生成器只识别 `text: >-`,于是 WSL 环境说明从未追加(会话仍在发行版内运行,但模型不知道自己的 cwd 是 Linux 路径)。现在按 `suffix` → `text` → `prefix` 依次补写(内联标量会先折成块标量,句子落在原 `text` 块的位置),带 `complete: true` 的 persona 依旧不动。
144
+ - **persona 文本位置变更**([#22](https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace/issues/22)):DSH 把 persona 面向模型的字段从 `text` 改为内联 `suffix` + 折叠 `prefix`,而变体生成器只识别 `text: >-`,于是 WSL 环境说明从未追加(会话仍在发行版内运行,但模型不知道自己的 cwd 是 Linux 路径)。现在按 `suffix` → `text` → `prefix` 依次补写(内联标量会先折成块标量,句子落在原 `text` 块的位置),带 `complete: true` 的 persona 依旧不动。
121
145
  - **帮助面板**:对话框新增「?」按钮,就地展示本构建声明的 DSH 版本(直接从 `package.json` 经宿主路由读取,不会与清单脱节)、插件的用法与特性,以及无法修复的已知限制。
122
146
  - **UNC 工作区终于能收到技能目录**:宿主技能提供者用 `fs.watch` 监视工作区,对 `\\wsl.localhost\...` 会抛 `EISDIR`,该次观测被判为不完整,而 `dsh-tool-skill` 在快照不完整时会丢弃**整条**目录消息,于是 WSL 会话的模型一个技能都看不到。现在生成预设时把 `skill-filesystem` 行的 `watch` 固定为 `false`(若该行已有 `config:` 就并入,源里自己声明了 `watch` 则不动),目录改为在会话启动时扫描一次。代价是不再实时刷新:会话运行中途加入的技能要等下一个会话(技能正文仍实时读取)。
123
147
  - **`verify-lib` 加固**:其注释/字符串剥离器会把注释里的孤立单引号与后面的引号配对、吞掉剩余 bundle,使所有 `node:*` 导入看起来都被 tree-shake 掉;现在引号规则遇换行即终止,与 JavaScript 字符串一致。
@@ -146,7 +170,7 @@ WSL 世界现在在"会话能察觉到的每一处"都与宿主一致,最后
146
170
 
147
171
  ### 0.3.2 — 2026-08-29
148
172
 
149
- - **WSL 会话注入嵌套项目的技能目录**([#10](https://github.com/6Mikao9/dsh-wsl-workspace/issues/10)):注册工作区之下的嵌套项目里的 `.dsh/skills` / `.agents/skills` 会带宿主的项目等级与来源一并发布,模型看到的目录与会话 cwd 就在项目里时一致;发现过程有深度与预算上限,会剪掉 `node_modules` 与点目录,且不改动非 WSL 会话。
173
+ - **WSL 会话注入嵌套项目的技能目录**([#10](https://github.com/dsh-wsl-workspace-maintainers/dsh-wsl-workspace/issues/10)):注册工作区之下的嵌套项目里的 `.dsh/skills` / `.agents/skills` 会带宿主的项目等级与来源一并发布,模型看到的目录与会话 cwd 就在项目里时一致;发现过程有深度与预算上限,会剪掉 `node_modules` 与点目录,且不改动非 WSL 会话。
150
174
  - **扫描根对齐宿主**:从项目子目录发起查询先就近解析 `.git` 祖先,深层 cwd 也能看到所属项目的技能,且不会泄漏该祖先之上的技能。
151
175
  - **加固**:技能根预算按次强制,`skills.registerProvider` 调用加了保护,宿主 `skills` 服务形状不同时不再拖垮插件加载。
152
176
  - **清理**:移除历史预构建 `lib/` chunk 中残留的死 vendor 代码(含内联的 schemastery 副本),并补充嵌套技能目录的回归测试与 TESTING.md 章节。
package/TESTING.md CHANGED
@@ -145,6 +145,70 @@ Because issue #35/#36 only reproduce under the Desktop host, a release that touc
145
145
  `NODE_OPTIONS="--import file:///…/child-process-hide.mjs"`, and compare the published
146
146
  release against the fixed build through `POST /wsl-workspace/api`.
147
147
 
148
+ ### The persistent shell on DSH Desktop (issue #40)
149
+
150
+ A release that touches the relay's interpreter — or anything else the PTY backend is told
151
+ to run — must be checked on the Desktop side too, because that is the only host where
152
+ `process.execPath` is not a node. DSH Desktop's host process is the packaged Electron
153
+ executable in node mode, and an Electron binary spawned under a ConPTY writes **no bytes at
154
+ all**, so the relay exits 0 with nothing on the stream and every `bash` call reports
155
+ `PTY shell exited during startup` while the file tools keep working.
156
+
157
+ Two levels, in order of cost:
158
+
159
+ 1. **Simulation, on the real binaries** (no Desktop install needed beyond the extracted
160
+ installer):
161
+
162
+ ```powershell
163
+ # 1. Download the official installer and extract it (7-Zip handles the NSIS payload):
164
+ # https://download.deepseek.com/desktop/dsh-latest-windows-x64.exe -> <dir>/
165
+ # 2. Point the simulation at it and run:
166
+ $env:DSH_DESKTOP_DIR = 'D:\path\to\extracted'
167
+ node .test-runs/desktop-pty-sim.mjs
168
+ ```
169
+
170
+ It runs `resolveRelayNode()` *inside* the extracted `DeepSeek Harness.exe` (node mode,
171
+ the Desktop's own argv and environment) and asserts it picks
172
+ `resources/runtime/primary-runtime/dependencies/node/bin/node.exe` rather than the
173
+ Electron executable — from the argv payload and, without it, from the
174
+ executable-relative lookup — then runs the real `lib/wsl-relay.js` under a real ConPTY
175
+ on both interpreters and asserts the Electron one gives 0 bytes and an exit while the
176
+ resolved one gives a live bash prompt. The extracted installer is ~1 GB; nothing is
177
+ installed and nothing is launched beyond that binary in node mode.
178
+
179
+ 2. **End to end inside the real Desktop.** This needs no installation: the unpacked installer
180
+ from step 1 *is* a runnable Electron application. Run it against a scratch `DSH_HOME` and
181
+ with remote debugging on, install the build through the Desktop's own plugin panel, and
182
+ drive the window over CDP:
183
+
184
+ ```powershell
185
+ $env:DSH_HOME = 'D:\scratch\dsh-desktop-home' # never the real profile
186
+ # copy settings.yaml / .credentials.yaml in, so the first-run welcome is skipped
187
+ Start-Process '<dir>\DeepSeek Harness.exe' -ArgumentList `
188
+ '--remote-debugging-port=9333','--remote-allow-origins=*','--user-data-dir=D:\scratch\ud'
189
+
190
+ node .test-runs/desktop-cdp.mjs 9333 text # read the window
191
+ node .test-runs/desktop-cdp.mjs 9333 eval .test-runs/<probe>.mjs
192
+ node .test-runs/desktop-cdp.mjs 9333 reload # the F5 check
193
+ ```
194
+
195
+ In the window: 插件 → 添加插件 → a local directory path (the unpacked tarball) → 安装 →
196
+ the switch on the installed card. Then run the six-item pass above. `bash` must reach a
197
+ prompt; `pwd` in a second, separate call must still be where the first one left the shell
198
+ (`cd /tmp` → `/tmp`), which is the persistence the Desktop lost in #40; and
199
+ `bash_background` + `job_list` must produce a job id and a `running`/`completed` status
200
+ (that path failed separately — see below). The boot log must contain
201
+ `dsh-wsl-workspace: persistent shell: relay interpreter is …`, naming the interpreter it
202
+ chose; if that line names the Electron executable, the log also lists every candidate it
203
+ rejected and why. Close the window and stop the process when done.
204
+
205
+ `bash_background` deserves its own line here because the Desktop run is what caught it:
206
+ the jobs registry resolves a job's `owner` with `ctx.agents.get(owner)`, so the owner must
207
+ be the session **id** (`agent.id`, as the host's own producers pass). Passing the agent
208
+ object — which `tests/wsl-jobs.test.ts` used to assert — fails at runtime with
209
+ `session "[object Object]" has no live agent`, and no unit test with a fake registry can
210
+ see that. The real run is the gate.
211
+
148
212
  ## Release checklist
149
213
 
150
214
  1. `pnpm build` — clears `lib/`, rebuilds it, and runs the verification gate.
@@ -158,7 +222,7 @@ release against the fixed build through `POST /wsl-workspace/api`.
158
222
  9. `npm run verify:install` — packs the tree and installs the tarball with **plain npm** into a scratch directory, with no pnpm and no host packages present. This is the gate that would have caught 0.7.0, whose `peerDependencies` made npm auto-install an unpublished package (`E404 @deepseek-ai/dsh-retention`): every other check and every real session goes through `dsh plugin add` (pnpm), which only *warns* about unmet peers and installs anyway. `prepublishOnly` runs it, so `npm publish` now refuses to ship a package that npm users cannot install.
159
223
  10. Install the tarball into a clean profile (`dsh plugin --profile web add <tarball>`), restart `dsh web`, and run the end-to-end checks above plus the nested-skill probe. When the compatibility manifest changes, also run `scripts/verify-dsh-compat.sh` for every declared release.
160
224
  11. For a release, install the *published* version by name into one isolated case per declared release and confirm each boots (the launcher only reports ready once the plugin's API route answers) — the check that proves the artifact on the registry, not just the local tree.
161
- 12. For a release, drive the **six-item frontend pass** on every declared release (see "The compatibility pass on every declared release" above), and — when the `wsl.exe` call path changed — the Desktop-wrapper comparison as well.
225
+ 12. For a release, drive the **six-item frontend pass** on every declared release (see "The compatibility pass on every declared release" above), and — when the `wsl.exe` call path changed — the Desktop-wrapper comparison as well. When the persistent-shell path changed (the relay, its interpreter, or the PTY rows the variant generates), also run the Desktop PTY simulation above.
162
226
  13. Confirm the artifact identity before publishing: the tarball from the release path, a fresh `npm pack`, and `npm pack --ignore-scripts` over the committed `lib/` must hash identically, and `npm run verify:install` must print `verify-install: OK`.
163
227
 
164
228
  ### The multi-release check harness
@@ -173,14 +237,16 @@ node .test-runs/harness.mjs <runId> 0.1.0-rc.7 0.1.5-rc.2 # prepare + check
173
237
  node .test-runs/harness.mjs <runId> --checks 0.1.5-rc.2 # re-check an existing case
174
238
  ```
175
239
 
176
- Four checks need a live WSL distribution (`skills-real`, `fs-real`, `relay-real`,
177
- `search-real`); they build their own fixtures under `/tmp/dsh-wsl-compat` (override
178
- with `WSL_COMPAT_ROOT`, and the distribution with `WSL_COMPAT_DISTRO`) and remove
179
- them again. `exec-shape` reproduces the DSH Desktop `child_process` wrapper (plain
180
- `exec`/`execFile` wrappers + `syncBuiltinESMExports()`, which strips
181
- `util.promisify.custom`) in a probe process and asserts both the wrapped and the plain
182
- shapes produce a correct `{ stdout, stderr }`. `host-api` needs a running `dsh web` for
183
- the case, so it is expected to fail in a sweep — point it at a live instance's
184
- `runtime.json` instead (an absolute path; it is 12/12 there). The `typecheck` check exits
185
- non-zero because of the pre-existing `tsc --noEmit` errors in this tree; the gate is that
186
- the count does not grow.
240
+ Five checks need a live WSL distribution (`skills-real`, `fs-real`, `relay-real`,
241
+ `search-real`, and `conpty-relay`); they build their own fixtures under
242
+ `/tmp/dsh-wsl-compat` (override with `WSL_COMPAT_ROOT`, and the distribution with
243
+ `WSL_COMPAT_DISTRO`) and remove them again. `exec-shape` reproduces the DSH Desktop
244
+ `child_process` wrapper (plain `exec`/`execFile` wrappers + `syncBuiltinESMExports()`, which
245
+ strips `util.promisify.custom`) in a probe process and asserts both the wrapped and the plain
246
+ shapes produce a correct `{ stdout, stderr }`. `conpty-relay` takes the case's
247
+ `runtime.json` so it can load that release's own node-pty, resolves the relay's interpreter
248
+ the way the plugin does, and requires a live bash prompt through a real ConPTY — the
249
+ invariant issue #40 broke. `host-api` needs a running `dsh web` for the case, so it is
250
+ expected to fail in a sweep — point it at a live instance's `runtime.json` instead (an
251
+ absolute path; it is 12/12 there). The `typecheck` check exits non-zero because of the
252
+ pre-existing `tsc --noEmit` errors in this tree; the gate is that the count does not grow.