dsh-plugin-wsl-env 0.4.0 → 0.5.0

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
@@ -12,6 +12,52 @@ reference below points at that record's numbering.
12
12
 
13
13
  ## [Unreleased]
14
14
 
15
+ ## [0.5.0] - 2026-10-04
16
+
17
+ One theme: telling the truth, in the reader's language. The Plugins page now
18
+ renders localized titles and descriptions for every row this bundle owns, the
19
+ README runs shallow to deep and carries an Architecture section in both
20
+ languages, the tab-title limitation is documented with its upstream proposal,
21
+ and the statements `hostSessions` had already falsified are gone.
22
+
23
+ ### Added
24
+
25
+ - **The plugin page can speak the bundle's language.** The bundle and each of
26
+ its four inserted rows (`subprocess`, `picker`, `shell-env`, `auto-preset`)
27
+ now export localized display metadata — `locale/<lang>.json` with a
28
+ `meta.title` / `meta.description` pair, exposed through `exports` per row —
29
+ which is what the Plugins page reads before falling back to a package.json
30
+ `description`. A Chinese UI now renders Chinese titles and descriptions for
31
+ every row this package owns; previously the rows fell back to raw module
32
+ specifiers (no description at all), and the two official rows it inserts
33
+ keep their upstream English text, which only upstream locale files can fix.
34
+ - **docs/UPSTREAM-TERMINAL-TITLE.md — the upstream proposal behind a documented
35
+ limitation, posted as
36
+ [deepseek-ai/deepseek-harness#8769](https://github.com/deepseek-ai/deepseek-harness/discussions/8769).** `subprocess-wsl.hostSessions` routes a Windows-folder session's
37
+ terminal to `powershell.exe`, but the tab still reads `WSL`: the controller
38
+ titles a tab from the composition's single shell profile before the provider
39
+ is consulted, the terminal spawn spec carries no display name, and a plugin
40
+ cannot bridge the gap (a patch layer asserts rather than renames a row's
41
+ module, and disabling the `terminal-controller` row fails web boot — its id
42
+ is what the client's `webTerminals` mount waits behind). The doc records the
43
+ gap, the three closed escape hatches with the web-boot failure that closed
44
+ the second one live, and the smallest upstream change that would close it.
45
+ LIMITATIONS.md gains the matching limitation paragraph with the workarounds
46
+ that do work: double-click rename, and a user-layer `shellCandidates` entry
47
+ whose manual selection is titled after itself.
48
+ - **The README carries an Architecture section** (both languages): the two
49
+ mount heights — composition-level `subprocess-wsl` and the preset realm's
50
+ `wsl-shell` / `wsl-fs` — with the per-session terminal routing and the
51
+ command and file execution paths in one diagram and two paragraphs.
52
+ [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) remains the full reference.
53
+
54
+ ### Fixed
55
+
56
+ - Stale statements that `hostSessions` had already falsified were corrected in
57
+ place: `cordis.patch.yml`'s consequence note and ARCHITECTURE.md's "the
58
+ terminal follows the app configuration" paragraph still described the
59
+ pre-routing behaviour.
60
+
15
61
  ## [0.4.0] - 2026-10-03
16
62
 
17
63
  One theme: the enterprise review's remediation cycle completes. The
package/README.md CHANGED
@@ -10,7 +10,7 @@ This plugin lets [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harn
10
10
 
11
11
  **Windows + WSL2 only.** One install command, no dependencies of its own. The environment applies per session, so a session on a Windows folder keeps its normal Windows environment.
12
12
 
13
- [Install](#install) · [Using it](#using-it) · [Configure](#configure) · [Sandbox](#sandbox) ·
13
+ [Install](#install) · [Using it](#using-it) · [Configure](#configure) · [Recipes](#recipes) · [Architecture](#architecture) · [Sandbox](#sandbox) ·
14
14
  [Troubleshooting](#troubleshooting) · [Development](#development) · [Documentation](#documentation)
15
15
 
16
16
  ## Install
@@ -42,6 +42,7 @@ Open `\\wsl.localhost\ubuntu\home\you\project` as the workspace, then ask the mo
42
42
  - **Commands** run as `wsl.exe -d <distro> --cd <linux dir> --exec <your login shell> -lc <command>`, so your `PATH`, `nvm`, `cargo`, `pyenv` and rc files apply. The shell is not hardcoded to bash.
43
43
  - **Files are the distro's real files.** `/home/you/x` and `\\wsl.localhost\ubuntu\home\you\x` are the same file, and `/mnt/c/...` reaches the Windows disk.
44
44
  - **The terminal** (right sidebar, then *New terminal*) opens a shell inside the distro, in the session's folder.
45
+ - **Port visibility.** The model sees which ports have a listener inside the distro (`DSH_WSL_PORTS`, refreshed about every 10 s), so it can hand you the exact URL of a dev server it just started; WSL2's localhost forwarding makes it reachable from Windows directly.
45
46
  - **The model sees its shell environment.** The plugin registers `DSH_WSL_DISTRO`, `DSH_WSL_SHELL` and `DSH_WSL_HOME` in the managed `DSH_*` namespace.
46
47
  - **Permissions work as on a Linux host.** The Permissions selector switches between `read-only`, `workspace-write` (the default) and `danger-full-access`. A refused command or write comes back with an offer to run it with wider permissions; if you approve, that one call runs without the sandbox.
47
48
 
@@ -62,22 +63,6 @@ Override a row by id in `$DSH_HOME/profiles/<name>/cordis.patch.yml`. The keys w
62
63
 
63
64
  [`cordis.patch.yml`](cordis.patch.yml) is the commented reference for every shipped value. [docs/CONFIGURATION.md](docs/CONFIGURATION.md) lists the rest, including `shell`, `loginShell`, `cwd`, `timeoutMs`, `resolveSymlinks`, `preferredDistro` and `maxEntries`; [examples/profile.cordis.patch.yml](examples/profile.cordis.patch.yml) is a machine-local layer to copy from.
64
65
 
65
- ## Sandbox
66
-
67
- Commands are confined by `bubblewrap` inside the distro, and file writes are checked against the same policy. The Windows ACL sandbox cannot be used here: its restricted token cannot reach WSL at all.
68
-
69
- | Mode | What a command inside the distro can do |
70
- |---|---|
71
- | `read-only` | read the whole distro; a fresh `/dev` is mounted writable, so `/dev/null` and `/dev/shm` work, and nothing else does |
72
- | `workspace-write` | the above, plus the session workspace is writable and `/tmp` is a temporary mount |
73
- | `danger-full-access` | no sandbox; used for an approved wider-permission request |
74
-
75
- **`bubblewrap` is required, and the failure is closed.** Without it every confined command reports `SANDBOX_UNAVAILABLE` instead of running unconfined. Set `sandbox: false` on either provider to opt out; the tool layer then tells the model these operations have no sandbox.
76
-
77
- **The reported enforcement is `partial`, not `full`.** A process inside the distro can still run a Windows program through WSL interop, for example `/mnt/c/.../*.exe`, and bubblewrap does not govern it. `npm run probe:sandbox` demonstrates the boundary on your machine.
78
-
79
- See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#sandbox) for the design, and [docs/LIMITATIONS.md](docs/LIMITATIONS.md) for everything the plugin does not do.
80
-
81
66
  ## Recipes
82
67
 
83
68
  - **Git credential sharing**: let git inside the distro use the Windows-side Git
@@ -95,6 +80,44 @@ See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#sandbox) for the design, and [do
95
80
  two Windows-path ones (`DSH_HOME`, `DSH_PROFILE_DIR`) with `/p`. `PATH` is
96
81
  deliberately never forwarded — it would shadow the distro's own PATH.
97
82
 
83
+ ## Architecture
84
+
85
+ One DSH process serves both kinds of session at once — a workspace on a Windows folder, and a workspace inside the distro — because its providers mount at two levels:
86
+
87
+ ```text
88
+ composition (app level, one per process)
89
+ ├─ subprocess-wsl the GUI terminal's execution world
90
+ │ WSL-folder session → the distro shell, in the session's Linux directory
91
+ │ Windows-folder session → powershell.exe, in the session's Windows directory
92
+ ├─ directory-picker-wsl installed distros listed beside the Windows home
93
+ ├─ wsl-shell-env the DSH_WSL_DISTRO / _SHELL / _HOME / _PORTS facts the model sees
94
+ └─ auto-preset binds the wsl preset when a session opens a distro folder
95
+
96
+ preset-wsl (the wsl agent preset; its services run in isolate realms)
97
+ ├─ wsl-shell ctx.shell — wsl.exe --exec <login shell>, confined by bubblewrap inside the distro
98
+ └─ wsl-fs ctx.fs — real distro files; agent substrate on ext4, or the 9p share
99
+ ```
100
+
101
+ **Why two levels.** `wsl-shell` and `wsl-fs` live inside the `wsl` agent preset, which `auto-preset` binds whenever a session's workspace is inside the distro: a Windows-folder session keeps the stock providers, a distro session gets the WSL ones — the environment is a property of the session, not of the process. The terminal controller is the exception: it resolves its execution world through the root context, which never sees a preset's isolate realms, so `subprocess-wsl` sits at the composition level instead.
102
+
103
+ **Commands and files.** A command runs as `wsl.exe -d <distro> --cd <linux dir> --exec <login shell> -lc <cmd>` inside a distro-side bubblewrap profile assembled with the same arguments as DSH's own Linux runner, so confinement semantics and error messages match a Linux host. The file tools read and write real distro files on one of two substrates: the default `agent` — a resident in-distro process, where reads, writes and identities run on ext4 with native symlinks and mode bits — or `share`, the opt-out: the Windows-side host stack over the 9p share. Both check writes against the same policy the command sandbox enforces.
104
+
105
+ ## Sandbox
106
+
107
+ Commands are confined by `bubblewrap` inside the distro, and file writes are checked against the same policy. The Windows ACL sandbox cannot be used here: its restricted token cannot reach WSL at all.
108
+
109
+ | Mode | What a command inside the distro can do |
110
+ |---|---|
111
+ | `read-only` | read the whole distro; a fresh `/dev` is mounted writable, so `/dev/null` and `/dev/shm` work, and nothing else does |
112
+ | `workspace-write` | the above, plus the session workspace is writable and `/tmp` is a temporary mount |
113
+ | `danger-full-access` | no sandbox; used for an approved wider-permission request |
114
+
115
+ **`bubblewrap` is required, and it fails closed.** Without it every confined command reports `SANDBOX_UNAVAILABLE` instead of running unconfined. Set `sandbox: false` on either provider to opt out; the tool layer then tells the model these operations have no sandbox.
116
+
117
+ **The reported enforcement is `partial`, not `full`.** A process inside the distro can still run a Windows program through WSL interop, for example `/mnt/c/.../*.exe`, and bubblewrap does not govern it. `npm run probe:sandbox` demonstrates the boundary on your machine.
118
+
119
+ See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md#sandbox) for the design, and [docs/LIMITATIONS.md](docs/LIMITATIONS.md) for everything the plugin does not do.
120
+
98
121
  ## Troubleshooting
99
122
 
100
123
  | Symptom | Cause | Fix |
package/cordis.patch.yml CHANGED
@@ -43,10 +43,12 @@
43
43
  # directory, so offering host executables there would advertise shells this
44
44
  # terminal never starts.
45
45
  #
46
- # Consequence: the terminal follows the composition, not the Session. A Session
47
- # whose workspace is a Windows folder gets the same distro terminal, started in
48
- # that folder as `/mnt/<drive>/…`. That is the honest limit of a per-session
49
- # execution world that no Session identity reaches.
46
+ # Consequence: a terminal's EXECUTION WORLD follows the Session — a WSL-folder
47
+ # session runs the distro's shell, a Windows-folder session runs PowerShell in
48
+ # its own directory (`hostSessions` on `subprocess-wsl`) — while the shell menu
49
+ # and the tab title stay the composition's single `WSL` profile, which no
50
+ # Session identity reaches. The title that implies is documented in
51
+ # docs/LIMITATIONS.md and bridged upstream in docs/UPSTREAM-TERMINAL-TITLE.md.
50
52
  # ═════════════════════════════════════════════════════════════════════════════
51
53
  - id: subprocess
52
54
  disabled: true
@@ -74,9 +74,13 @@ So the layer disables the shipped `subprocess` row and inserts
74
74
  overrides `spawnTerminal` only. Ordinary `spawn()`, the host ripgrep search, the
75
75
  pwsh executor, and the LSP host reach the shipped implementation unchanged.
76
76
 
77
- The cost is that the terminal follows the app configuration, not the session. A
78
- session opened on a Windows folder still gets a distro terminal, started in
79
- `/mnt/<drive>/...`.
77
+ The terminal's execution world follows the session (`hostSessions`, default on):
78
+ a WSL-folder session runs the distro shell, a Windows-folder session runs
79
+ `powershell.exe` in its own directory. What still follows the app configuration
80
+ is the shell menu and the tab title, both derived from the composition's single
81
+ `WSL` shell profile — so a host session's tab reads `WSL` over a PowerShell
82
+ process. See [LIMITATIONS.md](LIMITATIONS.md) and
83
+ [UPSTREAM-TERMINAL-TITLE.md](UPSTREAM-TERMINAL-TITLE.md).
80
84
 
81
85
  ## Path coordinates
82
86
 
@@ -32,6 +32,7 @@ are schema defaults, listed because they are the ones worth knowing.
32
32
  | `subprocess-wsl` | `distro` | `''` *(shipped)* | which distro the GUI terminal opens in |
33
33
  | | `shell` | `''` | pin a shell; empty lets `wsl.exe` decide |
34
34
  | | `loginShell` | `true` | whether a pinned shell uses login semantics |
35
+ | | `hostSessions` | `true` | give a session opened on a Windows folder a host `powershell.exe` in its own directory instead of the distro shell at `/mnt/<drive>/…`; `false` restores the composition-owned distro terminal for every session |
35
36
 
36
37
  You can override the other keys in the same way; they keep their defaults. Those are
37
38
  `wslPath`, `hostCwd` and `forwardEnv` on the shell and terminal rows,
@@ -110,5 +110,15 @@ Everything below applies to the share substrate unless it says otherwise.
110
110
  default on): a WSL-folder session gets the distro shell, a Windows-folder session gets
111
111
  `powershell.exe` in its own directory. The shell MENU remains the single configured
112
112
  profile, and a directory-less launch still lands in the distro.
113
+ - A host session's TAB TITLE still reads `WSL`, the composition's shell profile
114
+ name, over a PowerShell process. The controller titles a tab from that profile
115
+ before the provider rewrites the launch, and a plugin cannot correct it: a
116
+ patch layer asserts rather than renames a row's module, and the row's id is
117
+ load-bearing for the web composition (disabling it fails web boot with
118
+ "waiting for service: webTerminals"). The full account and the proposed
119
+ upstream change are in [UPSTREAM-TERMINAL-TITLE.md](UPSTREAM-TERMINAL-TITLE.md).
120
+ Meanwhile, double-click a tab's title to rename it, or add `shellCandidates`
121
+ to the `terminal-controller` row in your own profile layer — a manually
122
+ selected shell is titled after itself.
113
123
  - Terminal activity reporting stops at `wsl.exe`, so the controller never reclaims
114
124
  these terminals when they are idle.
package/docs/README.zh.md CHANGED
@@ -10,7 +10,7 @@
10
10
 
11
11
  **只支持 Windows + WSL2。** 安装只需一条命令,插件自身没有依赖。环境按会话生效:打开 Windows 文件夹的会话继续使用原来的 Windows 环境。
12
12
 
13
- [安装](#安装) · [使用](#使用) · [配置](#配置) · [沙箱](#沙箱) · [常见问题](#常见问题) ·
13
+ [安装](#安装) · [使用](#使用) · [配置](#配置) · [配方](#配方) · [架构](#架构) · [沙箱](#沙箱) · [常见问题](#常见问题) ·
14
14
  [开发](#开发) · [文档](#文档)
15
15
 
16
16
  ## 安装
@@ -63,6 +63,36 @@ dsh --profile wsl # 4. 启动
63
63
 
64
64
  [`cordis.patch.yml`](../cordis.patch.yml) 是每个随包值的注释参考。[docs/CONFIGURATION.md](CONFIGURATION.md) 列出其余键,包括 `shell`、`loginShell`、`cwd`、`timeoutMs`、`resolveSymlinks`、`preferredDistro` 和 `maxEntries`;[examples/profile.cordis.patch.yml](../examples/profile.cordis.patch.yml) 是一份可照抄的本机层。
65
65
 
66
+ ## 配方
67
+
68
+ - **Git 凭据共享**:让子系统里的 git 使用 Windows 侧的 Git Credential Manager,避免每次输密码:
69
+ `git config --global credential.helper "/mnt/c/Program\ Files/Git/mingw64/bin/git-credential-manager.exe"`
70
+ (路径按 Windows 侧 Git 的安装位置调整;WSL2 的 localhost 转发是平台行为,子系统内监听的端口 Windows 直接可达。)
71
+ - **路径与性能**:模型看到并操作的是子系统内的 Linux 路径(`/home/...`),写入子系统自身的 ext4;`/mnt/c` 通向 Windows 磁盘但走 9p,大批量小文件操作明显慢——重 IO 的项目请放在子系统文件系统内。`npm run bootstrap -- <子系统>` 会一并报告 ripgrep / inotifywait(搜索与监视的后端)是否就位。
72
+ - **WSLENV 透传**:WSL 只导入 `WSLENV` 中列出的变量。本插件按前缀放行托管的 `DSH_*` 命名空间,其中带 Windows 路径的两个(`DSH_HOME`、`DSH_PROFILE_DIR`)加 `/p` 让 WSL 翻译成 `/mnt/c/...`。`PATH` 故意不透传——否则 Windows 的 PATH 会覆盖子系统自身的 PATH。
73
+
74
+ ## 架构
75
+
76
+ 一个 DSH 进程同时服务两类会话:工作区开在 Windows 目录的,和工作区在子系统内的。能做到这一点,是因为 provider 分两层挂载:
77
+
78
+ ```text
79
+ 组合层(composition,每个进程一份)
80
+ ├─ subprocess-wsl GUI 终端的执行世界
81
+ │ 子系统目录会话 → 子系统 shell,落在会话的 Linux 目录
82
+ │ Windows 目录会话 → powershell.exe,落在会话自己的目录
83
+ ├─ directory-picker-wsl 在 Windows 家目录旁列出已安装的子系统
84
+ ├─ wsl-shell-env 向模型暴露 DSH_WSL_DISTRO / _SHELL / _HOME / _PORTS
85
+ └─ auto-preset 会话打开子系统目录时自动绑定 wsl preset
86
+
87
+ preset-wsl(wsl agent preset;其服务运行在 isolate realm 内)
88
+ ├─ wsl-shell ctx.shell — wsl.exe --exec <登录 shell>,在子系统内经 bubblewrap 约束
89
+ └─ wsl-fs ctx.fs — 子系统的真实文件;agent 基座落在 ext4,或走 9p 共享
90
+ ```
91
+
92
+ **为什么分两层。** `wsl-shell` 和 `wsl-fs` 位于 `wsl` agent preset 内,`auto-preset` 在会话工作区位于子系统内时自动绑定它:Windows 目录的会话继续使用原生 provider,子系统会话拿到 WSL provider——环境是会话的属性,而不是进程的。终端控制器是例外:它经根上下文解析执行世界,永远看不到 preset 的 isolate realm,所以 `subprocess-wsl` 必须挂在组合层。
93
+
94
+ **命令与文件。** 命令以 `wsl.exe -d <子系统> --cd <Linux 目录> --exec <登录 shell> -lc <命令>` 执行,外层是子系统内的 bubblewrap profile,参数与 DSH 自家 Linux runner 完全一致——约束语义与报错文案都和 Linux 主机相同。文件工具读写子系统的真实文件,基座二选一:默认 `agent`——常驻子系统内的进程,读写与路径解析落在 ext4,原生符号链接与权限位;或 `share`——退出项:Windows 侧宿主文件栈走 9p 共享。两个基座与命令沙箱使用同一份策略检查写入。
95
+
66
96
  ## 沙箱
67
97
 
68
98
  命令由**子系统内的 `bubblewrap`** 约束,文件写入按同一份策略检查。Windows ACL 沙箱在这里用不了:它的受限令牌完全到不了 WSL。
@@ -73,20 +103,12 @@ dsh --profile wsl # 4. 启动
73
103
  | `workspace-write` | 在上一行基础上,把会话工作区绑定为可写,并把 `/tmp` 挂成临时目录 |
74
104
  | `danger-full-access` | 不加沙箱;用于批准后的放宽权限请求 |
75
105
 
76
- **bubblewrap 是必需项,而且失败是关闭的。** 没有它时,每条受限命令都报 `SANDBOX_UNAVAILABLE`,而不是不受约束地运行。要退出约束就在任一 provider 上设 `sandbox: false`,工具层会如实告诉模型这些操作没有沙箱。
106
+ **bubblewrap 是必需项,缺失即失败关闭。** 没有它时,每条受限命令都报 `SANDBOX_UNAVAILABLE`,而不是不受约束地运行。要退出约束就在任一 provider 上设 `sandbox: false`,工具层会如实告诉模型这些操作没有沙箱。
77
107
 
78
108
  **上报的强制程度是 `partial` 而不是 `full`。** 子系统里的进程仍可经 WSL interop 执行 Windows 程序(例如 `/mnt/c/.../*.exe`),bubblewrap 管不到它。`npm run probe:sandbox` 会在你的机器上演示这条边界。
79
109
 
80
110
  设计见 [docs/ARCHITECTURE.md](ARCHITECTURE.md#sandbox),插件不做的事情见 [docs/LIMITATIONS.md](LIMITATIONS.md)。
81
111
 
82
- ## 配方
83
-
84
- - **Git 凭据共享**:让子系统里的 git 使用 Windows 侧的 Git Credential Manager,避免每次输密码:
85
- `git config --global credential.helper "/mnt/c/Program\ Files/Git/mingw64/bin/git-credential-manager.exe"`
86
- (路径按 Windows 侧 Git 的安装位置调整;WSL2 的 localhost 转发是平台行为,子系统内监听的端口 Windows 直接可达。)
87
- - **路径与性能**:模型看到并操作的是子系统内的 Linux 路径(`/home/...`),写入子系统自身的 ext4;`/mnt/c` 通向 Windows 磁盘但走 9p,大批量小文件操作明显慢——重 IO 的项目请放在子系统文件系统内。`npm run bootstrap -- <子系统>` 会一并报告 ripgrep / inotifywait(搜索与监视的后端)是否就位。
88
- - **WSLENV 透传**:WSL 只导入 `WSLENV` 中列出的变量。本插件按前缀放行托管的 `DSH_*` 命名空间,其中带 Windows 路径的两个(`DSH_HOME`、`DSH_PROFILE_DIR`)加 `/p` 让 WSL 翻译成 `/mnt/c/...`。`PATH` 故意不透传——否则 Windows 的 PATH 会覆盖子系统自身的 PATH。
89
-
90
112
  ## 常见问题
91
113
 
92
114
  | 现象 | 原因 | 处理 |
@@ -0,0 +1,81 @@
1
+ # Upstream proposal: a terminal tab title that follows the session's shell
2
+
3
+ Status: proposed upstream as
4
+ [deepseek-ai/deepseek-harness#8769](https://github.com/deepseek-ai/deepseek-harness/discussions/8769)
5
+ (targeting `packages/api/terminal-controller`, version line `0.2.0-rc.x`). This
6
+ is the record of a gap this plugin worked around, could not work around, and
7
+ now documents: the GUI terminal titles every tab from the composition's single
8
+ shell profile, so a per-session shell choice is invisible in the title.
9
+
10
+ ## The gap
11
+
12
+ `TerminalController.spawn` fixes a terminal's display title from the resolved
13
+ shell profile before the provider is ever consulted:
14
+
15
+ ```ts
16
+ const info: WebTerminalInfo = {
17
+ id: request.id, shell, title: shell.name, cwd: environment.cwd, ...
18
+ }
19
+ ```
20
+
21
+ `shell` is `resolveShell(subprocess, this.config.shell, signal)` — the
22
+ composition's configured profile (`shell.name: WSL` in this plugin's layer),
23
+ kept verbatim; `resolveExecutable` swaps only the `path`. Two properties of the
24
+ seam make the title unable to follow a per-session shell:
25
+
26
+ - `SubprocessTerminalSpawnSpec` carries no display-name field, so a provider
27
+ that rewrites the launch (this plugin routes a Windows-folder session's
28
+ `wsl.exe` launch to `powershell.exe` — `subprocess-wsl.hostSessions`) cannot
29
+ say what it actually started.
30
+ - The controller never reads the effective program back from the
31
+ `SubprocessTerminalHandle`, so the rewrite is invisible to `info.title`.
32
+
33
+ The result on this plugin's default configuration: a terminal that runs
34
+ PowerShell under a tab that reads `WSL`.
35
+
36
+ ## Why a plugin cannot bridge it
37
+
38
+ Three escape hatches were evaluated against `0.2.0-rc.2` and each is closed:
39
+
40
+ 1. **Replace the controller row's module with a subclass** that corrects the
41
+ title after `super.spawn` (the class exports itself; `BrowserTerminal.rename`
42
+ is public and lands before `create()` publishes the terminal). Closed by the
43
+ patch-layer contract: *"A truthy name asserts the existing plugin name
44
+ rather than renaming it"* (`packages/boot/app-boot/src/config-schema/document.ts`)
45
+ — an id-targeted patch can replace `config`, but not the module.
46
+ 2. **Disable the shipped row and insert the subclass under a new id.** Closed
47
+ by the web composition: the row id is load-bearing. The web-app bundle
48
+ inserts `terminal-controller`, and the client model that the
49
+ `dsh-client-ui-sidebar-terminal` entry waits for mounts with
50
+ `inject = ['remote', 'remote.terminal']` — the `terminal` namespace the host
51
+ controller registers in its constructor. Disabling the row fails web boot:
52
+ `1 entry did not activate: @deepseek-ai/dsh-client-ui-sidebar-terminal:
53
+ pending (waiting for service: webTerminals)`. Verified live against
54
+ `dsh-desktop 0.2.0-rc.2`.
55
+ 3. **Per-session config.** The `shell` profile is composition state; `!!js`
56
+ expressions evaluate once at config resolution and reach no Session
57
+ identity.
58
+
59
+ ## The proposal
60
+
61
+ Any one of these closes the gap; the first is the smallest and keeps the
62
+ profile name as the fallback:
63
+
64
+ - `SubprocessTerminalSpawnSpec` gains an optional `displayTitle` (or the handle
65
+ exposes the launched `argv`), and `create()` uses it for `info.title` when
66
+ present — falling back to `shell.name`. A provider that rewrites a launch
67
+ states the program it started; every other launch keeps the profile name.
68
+ - Alternatively, the controller derives the title from the effective program
69
+ itself: title `executableName(effectiveArgv[0])` when it differs from
70
+ `shell.path`, else `shell.name`.
71
+
72
+ With either, this plugin deletes the limitation paragraph in
73
+ [LIMITATIONS.md](LIMITATIONS.md) and the `WSL`-over-PowerShell title, for any
74
+ provider that rewrites launches — not just WSL.
75
+
76
+ ## Workarounds until then
77
+
78
+ - Double-click a tab's title to rename it (a shipped client feature; the
79
+ controller's `rename` Remote API accepts 1–120 characters).
80
+ - Add `shellCandidates` to the `terminal-controller` row in a user layer: a
81
+ manually selected shell is titled after itself.
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "WSL preset auto-selection",
4
+ "description": "Binds the WSL preset to a new session whose workspace directory is inside a distro, and never overrides an explicit operator choice."
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "WSL preset 自动选择",
4
+ "description": "当新会话的工作目录位于发行版内时,为它绑定 WSL preset;操作者的显式选择不会被覆盖。"
5
+ }
6
+ }
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "WSL execution environment",
4
+ "description": "Runs a DeepSeek Harness session against a WSL distro: shell, file reads/writes and command execution happen inside the distro, the directory picker and the GUI terminal target distro paths, and commands are confined by in-distro bubblewrap."
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "WSL-aware directory picker",
4
+ "description": "The workspace picker for both worlds: its root level lists the Windows home directory and every installed distro, so host and WSL workspaces share one dialog. The directory it returns is what auto-preset reads to choose the environment."
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "WSL 目录选择器",
4
+ "description": "同时服务主机与 WSL 两个世界的工作区选择器:根层级列出 Windows 主目录和每个已安装的发行版,两类工作区共用同一个对话框。它返回的目录也是 auto-preset 选择执行环境的依据。"
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "WSL shell environment facts",
4
+ "description": "Contributes the WSL environment's facts (distro, real login shell, home, listening ports) to the managed DSH_* namespace injected into every model shell call — for sessions whose workspace is inside a distro."
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "WSL shell 环境事实",
4
+ "description": "把 WSL 环境的事实(发行版、真实登录 shell、home、监听端口)贡献给注入每次模型 shell 调用的 DSH_* 托管命名空间——只对工作区位于发行版内的会话生效。"
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "WSL subprocess provider",
4
+ "description": "The composition-level ctx.subprocess backend: rewrites the GUI terminal's wsl.exe launch into the pinned distro and Linux directory, so the right-hand terminal window opens inside WSL."
5
+ }
6
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "WSL 子进程提供者",
4
+ "description": "组合级的 ctx.subprocess 后端:把 GUI 终端的 wsl.exe 启动改写到指定发行版与 Linux 目录,让右侧终端窗口直接落在 WSL 里。"
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "WSL 执行环境",
4
+ "description": "让 DeepSeek Harness 以 WSL 子系统作为会话的执行环境:shell、文件读写与命令执行均在子系统内完成,目录选择器与 GUI 终端同样定位到子系统路径,并通过子系统内的 bubblewrap 对命令进行沙箱隔离"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-wsl-env",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "让 DeepSeek Harness 以 WSL 子系统作为会话的执行环境:shell、文件读写与命令执行均在子系统内完成,目录选择器与 GUI 终端同样定位到子系统路径,并通过子系统内的 bubblewrap 对命令进行沙箱隔离",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -23,7 +23,17 @@
23
23
  "./wsl": "./lib/wsl.js",
24
24
  "./paths": "./lib/paths.js",
25
25
  "./agent": "./lib/agent.js",
26
- "./package.json": "./package.json"
26
+ "./package.json": "./package.json",
27
+ "./locale/en.json": "./locale/en.json",
28
+ "./locale/zh.json": "./locale/zh.json",
29
+ "./subprocess/locale/en.json": "./locale/subprocess/en.json",
30
+ "./subprocess/locale/zh.json": "./locale/subprocess/zh.json",
31
+ "./picker/locale/en.json": "./locale/picker/en.json",
32
+ "./picker/locale/zh.json": "./locale/picker/zh.json",
33
+ "./shell-env/locale/en.json": "./locale/shell-env/en.json",
34
+ "./shell-env/locale/zh.json": "./locale/shell-env/zh.json",
35
+ "./auto-preset/locale/en.json": "./locale/auto-preset/en.json",
36
+ "./auto-preset/locale/zh.json": "./locale/auto-preset/zh.json"
27
37
  },
28
38
  "dsh": {
29
39
  "bundle": {
@@ -33,6 +43,7 @@
33
43
  "files": [
34
44
  "lib/**/*.js",
35
45
  "agent/*.sh",
46
+ "locale/**/*.json",
36
47
  "cordis.patch.yml",
37
48
  "examples/**/*.yml",
38
49
  "README.md",
@@ -42,6 +53,7 @@
42
53
  "docs/PARITY.md",
43
54
  "docs/PEER-PARITY.md",
44
55
  "docs/UPSTREAM-FSIO-EXPORT.md",
56
+ "docs/UPSTREAM-TERMINAL-TITLE.md",
45
57
  "docs/ARCHITECTURE.md",
46
58
  "CHANGELOG.md",
47
59
  "CONTRIBUTING.md",