dsh-win-multi-bash 0.2.0 → 0.3.1

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.i18n.yaml CHANGED
@@ -1,8 +1,7 @@
1
- # Bilingual-pair consistency record (docs/i18n.md, deepseek-harness repo): the
2
- # git blob hash of each side as of the last confirmed-consistent state. Both
3
- # languages carry equal authority; after editing either side, bring the other
4
- # along and re-record with the harness docs tooling (not shipped with this
5
- # package):
6
- # node scripts/verify-docs.mjs --write <dir> # run from the deepseek-harness checkout
7
- README.md: 498a99ec73bd8a0015e2737d54696eb04d0989d6
8
- README.zh.md: 7fcc09ebfab371d4236645737710808cbf22c7c9
1
+ # Bilingual-pair consistency record for this package: the git blob hash of each
2
+ # side as of the last confirmed-consistent state (whole-file, unlike the
3
+ # deepseek-harness per-section format). Both languages carry equal authority;
4
+ # after editing either side, bring the other along and re-record both hashes:
5
+ # git hash-object README.md README.zh.md
6
+ README.md: ccee00c06fbb2956f5a0559b2b74710e4d4eed55
7
+ README.zh.md: bfdcd780500df1a9bd67e9e07aece381cd52e0fd
package/README.md CHANGED
@@ -2,17 +2,18 @@ English | [中文](README.zh.md)
2
2
 
3
3
  # dsh-win-multi-bash
4
4
 
5
- A Windows multi-bash plugin for DeepSeek Harness: `git_bash` / `wsl_bash` model tools plus a `shell-select` executor that routes the single `ctx.shell` seat across Git Bash, WSL and pwsh. Pwsh stays the default, so existing behavior is unchanged until a bash-family tool is called.
5
+ A Windows multi-bash plugin for DeepSeek Harness: the `git_bash` / `wsl_bash` model tools, each owning its own Git Bash / WSL executor. The plugin never touches the `ctx.shell` seat — dsh's own pwsh executor keeps it — so pwsh behavior is identical to a deployment without the plugin.
6
6
 
7
7
  ## What it provides
8
8
 
9
9
  | Tool | Backend | Dialect | Notes |
10
10
  | --- | --- | --- | --- |
11
- | `git_bash` | git-bash | MSYS | `request.shell: 'git-bash'`, Git for Windows toolchain |
12
- | `wsl_bash` | wsl-bash | Linux | `request.shell: 'wsl-bash'`, WSL distro Linux userland |
13
- | `pwsh` (existing) | pwsh | — | Selector default route; behavior identical to a deployment without the plugin |
11
+ | `git_bash` | git-bash | MSYS | Owns a `GitBashExecutor`; Git for Windows toolchain |
12
+ | `wsl_bash` | wsl-bash | Linux | Owns a `WslBashExecutor`; WSL distro Linux userland |
13
+ | `pwsh` (dsh's own) | pwsh | — | Served by the base bundle's `pwsh-sandbox` row, untouched by this plugin |
14
14
 
15
- - `shell-select` occupies the single `ctx.shell` seat and routes `request.shell ?? default` to one backend; `default` stays `pwsh`.
15
+ - Each tool constructs and drives its own executor; neither registers as `ctx.shell`, so the seat — and every plugin that injects `shell` (dsh's `tool-pwsh`, `permission-presets`, …) — is unaffected by this plugin being loaded.
16
+ - Why there is no selector any more: dsh 0.1.7 dropped `ShellExecRequest.shell`, the routing field the pre-0.1.7 `shell-select` design dispatched on. With no routing input left, the tool is what knows which shell it wants — so the selector has nothing to select on, and is gone.
16
17
  - Executable resolution and sandbox probing are lazy: a host without Git Bash or WSL does not affect pwsh; failures are loud at first use.
17
18
  - Git Bash is found automatically, in order: an explicit `gitBash.bashPath`, Git install roots inferred from `git.exe` layout directories on PATH (so an install reachable through `git` is found without a pin, even outside the well-known locations), the well-known Program Files layout on every fixed drive (`C:\Program Files\Git`, `D:\Program Files\Git`, ...), `bash.exe` on PATH, and finally the `HKLM\SOFTWARE\GitForWindows` install path (which the Git for Windows installer always records, covering portable installs). The Windows WSL launcher `System32\bash.exe` and `WindowsApps` app-execution alias directories are **never** selected, and candidates must be real regular files — symlinks/reparse points are rejected — so a stale WSL `bash.exe` alias can never shadow a real Git Bash (this tool is MSYS, not WSL).
18
19
  - Sandbox `auto`: Git Bash probes the windows-acl runner, WSL probes `bwrap` inside the distro; a failed probe degrades honestly to an unconfined run with no sandbox facts. An explicit `sandbox: bwrap` with bubblewrap missing fails loudly at the first `wsl_bash` command (never at boot), leaving the other backends untouched.
@@ -24,13 +25,13 @@ The three backends do **not** share the same file-sandbox capability:
24
25
 
25
26
  | Backend | Mechanism | enforcement | On probe failure |
26
27
  | --- | --- | --- | --- |
27
- | `pwsh` | windows-acl restricted-token runner | partial | no probe — always confined |
28
+ | `pwsh` | windows-acl restricted-token runner | partial | no probe — always confined (dsh's own executor) |
28
29
  | `wsl_bash` | `bwrap` (bubblewrap) inside the distro | full | runs unconfined, no sandbox facts |
29
30
  | `git_bash` | windows-acl runner wrapping MSYS bash | partial (when the probe passes) | runs unconfined, no sandbox facts when the probe fails |
30
31
 
31
32
  > ⚠️ **`git_bash` usually cannot be sandboxed in Git for Windows deployments.** The windows-acl runner fails to launch the MSYS `bash.exe` under a restricted token (`CreateProcessAsUserW` returns Win32 error 2; `cmd.exe` and `pwsh.exe` launch fine). With `sandbox: auto`, a failed probe degrades to an **unconfined run** by contract. **Do not assume `git_bash` is protected by the DSH sandbox** — for sensitive operations use `pwsh` (restricted token active) or `wsl_bash` (bwrap active), or take the explicit escalation-approval path.
32
33
  >
33
- > ⚠️ **`wsl_bash` sandboxing depends on bubblewrap inside the distro.** Without bwrap, `auto` degrades to unconfined as well; the probe verdict is cached for the **host process lifetime** — after installing bwrap you must restart `dsh web` (or touch the shell settings section to trigger a backend rebuild) before it is re-probed.
34
+ > ⚠️ **`wsl_bash` sandboxing depends on bubblewrap inside the distro.** Without bwrap, `auto` degrades to unconfined as well; the probe verdict is cached for the **host process lifetime** — after installing bwrap you must restart `dsh web` (or reload the tool row by editing the profile patch) before it is re-probed.
34
35
  >
35
36
  > ⚠️ **A denial is only classified when the command exits non-zero.** If a blocked write is followed by a successful command (`echo nope > /etc/x; echo done`), the overall exit is 0 and no `[sandbox: file access denied]` marker is emitted — matching the upstream bash-sandbox rule to avoid false positives.
36
37
  >
@@ -38,12 +39,17 @@ The three backends do **not** share the same file-sandbox capability:
38
39
  > **`requireSandbox`: refuse unconfined runs when the probe fails (optional hardening).** Both backends support `requireSandbox: true` (default `false`, keeping the existing degrade-and-run behavior). When enabled, a failed probe (windows-acl unusable for git-bash / bwrap missing for wsl-bash) means: `danger-full-access` runs as usual (an unconfined run is equivalent to an explicit full-access grant), while `read-only` / `workspace-write` calls are **refused** with an error naming the fix and the escalation path. The tool layer also advertises the sandbox and opens the `sandbox_permissions` argument, so the model can take the approval-based escalation. Example:
39
40
 
40
41
  > ```yaml
41
- > # the win-mb-shell-select row in cordis.patch.yml
42
- > config:
43
- > backends: [git-bash, wsl-bash, pwsh]
44
- > default: pwsh
45
- > gitBash: { requireSandbox: true }
46
- > wslBash: { requireSandbox: true }
42
+ > # the win-mb-tool-git / win-mb-tool-wsl rows in cordis.patch.yml:
43
+ > # each tool row carries only its own backend's partition
44
+ > - id: win-mb-tool-git
45
+ > name: 'dsh-win-multi-bash/tool-git-bash'
46
+ > config:
47
+ > gitBash: { requireSandbox: true }
48
+ >
49
+ > - id: win-mb-tool-wsl
50
+ > name: 'dsh-win-multi-bash/tool-wsl-bash'
51
+ > config:
52
+ > wslBash: { requireSandbox: true }
47
53
  > ```
48
54
 
49
55
  > `requireSandbox` and `sandbox: none` are mutually exclusive in intent — explicit `none` is a deliberate opt-out and stays allowed; `requireSandbox` only governs the "sandbox wanted but probe failed" case.
@@ -60,11 +66,13 @@ wsl.exe -d Ubuntu-24.04 -e bash -c "command -v bwrap && bwrap --version" # ver
60
66
  - The probe targets the **first distro** from `wsl -l -q`; if your target distro is not the first, pin it via `wslBash.wslDistro` in `cordis.patch.yml` and install bwrap **inside that distro** (e.g. `Ubuntu-24.04`; `docker-desktop` has no bash and cannot be used).
61
67
  - `sudo` may require a password (depending on the distro's sudoers configuration); use `apt-get install -y` for scripting.
62
68
  - Other distro families: Fedora `dnf install bubblewrap`, Alpine `apk add bubblewrap`.
63
- - After installing you **must restart `dsh web`** (or touch the shell settings section to rebuild backends) — the probe verdict is cached for the host process lifetime, and `wsl_bash` stays unconfined until then.
69
+ - After installing you **must restart `dsh web`** (or reload the tool row by editing the profile patch) — the probe verdict is cached for the host process lifetime, and `wsl_bash` stays unconfined until then.
64
70
 
65
71
  ## Tool prompts (model-facing descriptions)
66
72
 
67
- The `git_bash` / `wsl_bash` tool descriptions are deliberately concise and mirror the official `tool-pwsh` skeleton: a fresh shell per call, the dialect's paths/env form, `[exit code: N]` markers, `$DSH_*` environment facts, sandbox behavior, output truncation, background jobs, and the escalation contract. The longer dialect notes (MSYS path rewriting, WSL base64 payloads) live in this README rather than in the model-facing text.
73
+ The `git_bash` / `wsl_bash` tool descriptions are deliberately concise and mirror the official `tool-pwsh` skeleton: a fresh shell per call, the dialect's paths/env form, `[exit code: N]` markers, `$DSH_*` environment facts, sandbox behavior, output truncation, delete/move target verification, the unset-variable `${VAR:?}` guard, background jobs, and the escalation contract. The longer dialect notes (MSYS path rewriting, WSL base64 payloads) live in this README rather than in the model-facing text.
74
+
75
+ Both tools run `bash -c`, so that safety guidance is worded as in `tool-bash`, not as in `tool-pwsh`: `$HOME` is an ordinary assignable variable in bash, so pwsh's "do not assign to automatic variables" sentence would be wrong here — the bash guard for a computed path is the `${VAR:?}` form above, which makes an unset variable fail instead of silently expanding to an empty string.
68
76
 
69
77
  `git_bash`'s description additionally carries a path-format hint:
70
78
 
@@ -72,6 +80,10 @@ The `git_bash` / `wsl_bash` tool descriptions are deliberately concise and mirro
72
80
 
73
81
  So when a command prints an MSYS path (e.g. `/d/WorkSpace/foo`), convert it to its Windows form (`D:\WorkSpace\foo`) before handing it to dsh's file tools; inside the bash command itself, MSYS paths are what the shell expects.
74
82
 
83
+ **`workdir` accepts native, MSYS and WSL-automount forms.** A model told that the dialect's paths are MSYS- or WSL-shaped will naturally write `workdir` that way, so the resolver translates a single-letter drive form (`/c/...`) and the WSL automount form (`/mnt/c/...`) into the native `C:\...` before the executor hands the value to `spawn`; MSYS POSIX roots (`/etc`, `/usr`, `/tmp`) and distro-side paths such as `/mnt/data` are left untouched, and a relative `workdir` still resolves against the session workspace. Before that translation an MSYS-form `workdir` failed as `spawn <shell> ENOENT` — a missing-shell symptom for what is really an unusable cwd.
84
+
85
+ Each tool row also registers a short system-prompt section (`tool:<name>`): check the `[exit code: N]` marker on every result, and chain dependent steps with `&&` or `set -o pipefail` — `;` never stops on failure, and `cmd | tail` returns the status of `tail`, not of `cmd`. That is guidance about *composing* a multi-step command rather than about one call's arguments, so it belongs to the prompt instead of the per-call schema; it also makes the runtime's own tail truncation the reason not to bound output with a pipe.
86
+
75
87
  ## Path conversion (MSYS auto-rewriting)
76
88
 
77
89
  Git Bash rewrites leading-slash POSIX paths into Windows paths (e.g. `<Git root>\root`) whenever a native Windows program is called — standard MSYS behavior, not a plugin defect. Calling `wsl.exe` (or any native exe) with POSIX paths from inside `git_bash` therefore fails:
@@ -91,14 +103,14 @@ The full feature implementation ships in `lib/` as plain ESM JS — no build ste
91
103
 
92
104
  ```
93
105
  lib/
94
- ├── shell-select/ ShellSelectExecutor (the ctx.shell selector)
95
106
  ├── bash-git/ GitBashExecutor (MSYS)
96
107
  ├── bash-wsl/ WslBashExecutor (WSL, base64 payloads)
97
- ├── tool-bash/ tool factory + git_bash / wsl_bash instances
108
+ ├── tool-bash/ tool factory + git_bash / wsl_bash instances,
109
+ │ backend ownership (types/backend.js)
98
110
  └── vendor/ helper modules for runner-failure classification and bwrap profiles
99
111
  ```
100
112
 
101
- When the deployment's base bundle already provides its own `shell-select` row, the patch disables that row and lets this plugin's selector own the seat (two providers would conflict). On base bundles without it, the entry is a harmless no-op.
113
+ Because no row of this plugin registers as `ctx.shell`, the patch inserts only its own two tool rows and disables nothing: the base bundle's shell wiring (`pwsh-sandbox` on win32, `bash-sandbox` elsewhere) stays exactly as shipped, and pwsh keeps working whether or not this plugin is loaded.
102
114
 
103
115
  ## Prerequisites
104
116
 
@@ -141,7 +153,13 @@ Requires `pnpm` (dsh plugin is a pnpm forwarder); the bundle layer is assembled
141
153
  powershell -ExecutionPolicy Bypass -File .\smoke\run.ps1
142
154
  ```
143
155
 
144
- Boots a real composition over the profile runtime (modifying nothing), verifies `git_bash` / `wsl_bash` register and execute real commands — including an explicit `bashPath` variant. Requires node >= 20.
156
+ Boots a real composition over the profile runtime (modifying nothing), verifies `git_bash` / `wsl_bash` register and execute real commands — including an explicit `bashPath` variant — and asserts `ctx.shell` is still provided by the base bundle's own row (the regression this plugin once caused). Requires node >= 20.
157
+
158
+ ```powershell
159
+ powershell -ExecutionPolicy Bypass -File .\smoke\audit.ps1
160
+ ```
161
+
162
+ Runs the full audit suite instead: pure-unit coverage of the vendor helpers, executor internals and tool config schemas, plus boot-integration matrices for the real `cordis.patch.yml` and for misconfigurations.
145
163
 
146
164
  ## Troubleshooting
147
165
 
@@ -155,14 +173,14 @@ Boots a real composition over the profile runtime (modifying nothing), verifies
155
173
  | `wsl_bash` fails with `bwrap was not found` | `sandbox: bwrap` is set but bubblewrap is missing inside the distro: install it per “Sandbox behavior → Enabling the bwrap sandbox for `wsl_bash`” (`sudo apt-get install -y bubblewrap`) and restart `dsh web`, or use `sandbox: auto` / `none` |
156
174
  | `wsl_bash` sandbox reports a runner failure on bwrap | The bwrap workspace root is the Linux side of a Windows drive path (`/mnt/<drive>/...`): a UNC workspace root fails loud, and a distro with a custom automount root (wsl.conf `automount.root`) needs a matching configuration |
157
175
  | Calling `wsl.exe` (or other native exes) with POSIX paths from `git_bash` reports `No such file or directory` | MSYS rewrote `/root` etc. to `<Git root>\root`: prefix with `MSYS_NO_PATHCONV=1` / `MSYS2_ARG_CONV_EXCL="*"`, or use a `//` prefix; use the `wsl_bash` tool for WSL work |
158
- | `wsl_bash` still runs without a sandbox after installing bubblewrap | The bwrap probe verdict is cached for the host process lifetime: restart `dsh web`, or touch the shell settings section to trigger a backend rebuild |
159
- | `shell-select: backend "x" is not enabled` | The `backends` list does not match the tool names; keep `backends: [git-bash, wsl-bash, pwsh]` |
176
+ | `wsl_bash` still runs without a sandbox after installing bubblewrap | The bwrap probe verdict is cached for the host process lifetime: restart `dsh web`, or reload the tool row by editing the profile patch |
177
+ | Boot warns `win-mb-tool-git … waiting for service: shell` (or dsh's own `tool-pwsh` / `permission-presets` do) | Nothing provides `ctx.shell`. This plugin no longer provides it either: check the base bundle's `pwsh-sandbox` row is not disabled by some other patch |
160
178
 
161
179
  ## Layout
162
180
 
163
181
  ```
164
182
  dsh-win-multi-bash/
165
- ├── package.json # dsh.bundle manifest; exports ./shell-select ./tool-git-bash ./tool-wsl-bash
183
+ ├── package.json # dsh.bundle manifest; exports ./tool-git-bash ./tool-wsl-bash
166
184
  ├── cordis.patch.yml # the composition wiring (documented inline)
167
185
  ├── install.ps1 # Path A hot plug (junctions + managed block + Git Bash detection)
168
186
  ├── uninstall.ps1 # Path A hot unplug
package/README.zh.md CHANGED
@@ -2,17 +2,18 @@
2
2
 
3
3
  # dsh-win-multi-bash
4
4
 
5
- 适用于 DeepSeek Harness 的 Windows multi-bash 插件:`git_bash` / `wsl_bash` 模型工具 + `shell-select` 执行器,在唯一的 `ctx.shell` 席位上路由 Git Bash、WSL 与 pwsh。pwsh 保持默认,未调用 bash 系工具前现有行为完全不变。
5
+ 适用于 DeepSeek Harness 的 Windows multi-bash 插件:`git_bash` / `wsl_bash` 两个模型工具,各自持有自己的 Git Bash / WSL 执行器。插件不碰 `ctx.shell` 席位——该席位仍由 dsh 自带的 pwsh 执行器持有——因此 pwsh 行为与未装插件时完全一致。
6
6
 
7
7
  ## 功能一览
8
8
 
9
9
  | 工具名 | 后端 | 方言 | 说明 |
10
10
  | --- | --- | --- | --- |
11
- | `git_bash` | git-bash | MSYS | `request.shell: 'git-bash'`,Git for Windows 工具链 |
12
- | `wsl_bash` | wsl-bash | Linux | `request.shell: 'wsl-bash'`,WSL 发行版内 Linux userland |
13
- | `pwsh`(原有) | pwsh | — | 选择器默认路由,行为与未装插件时完全一致 |
11
+ | `git_bash` | git-bash | MSYS | 自带 `GitBashExecutor`;Git for Windows 工具链 |
12
+ | `wsl_bash` | wsl-bash | Linux | 自带 `WslBashExecutor`;WSL 发行版内 Linux userland |
13
+ | `pwsh`(dsh 自带) | pwsh | — | 由基座的 `pwsh-sandbox` 行提供,本插件完全不介入 |
14
14
 
15
- - `shell-select` 占据唯一的 `ctx.shell` 席位,按 `request.shell ?? default` 路由;`default` 保持 `pwsh`。
15
+ - 每个工具自行构建并驱动自己的执行器,都不注册为 `ctx.shell`;因此该席位——以及所有 inject `shell` 的插件(dsh 的 `tool-pwsh`、`permission-presets` 等)——不受本插件加载影响。
16
+ - 为什么不再有选择器:dsh 0.1.7 删除了 `ShellExecRequest.shell`,也就是 0.1.7 之前 `shell-select` 用来路由的字段。路由输入没了,只有工具自己知道要哪个 shell——选择器已无从可选,随之取消。
16
17
  - 可执行文件解析与沙箱探测全部惰性化:未安装 Git Bash / WSL 不影响 pwsh,首次使用时才响亮报错。
17
18
  - Git Bash 自动查找,顺序为:显式 `gitBash.bashPath` → 从 PATH 上 `git.exe` 布局目录反推的 Git 安装根(因此通过 `git` 可达的安装无需钉定即可找到,即使不在常见位置)→ 每个固定盘上的常见 Program Files 布局(`C:\Program Files\Git`、`D:\Program Files\Git` 等)→ PATH 上的 `bash.exe` → 最后读取 `HKLM\SOFTWARE\GitForWindows` 注册表安装路径(Git for Windows 安装器必写该键,覆盖便携安装)。Windows 的 WSL 启动器 `System32\bash.exe` 与 `WindowsApps` 应用执行别名目录**绝不入选**,且候选必须是真实普通文件——符号链接 / reparse point 一律拒绝——因此失效的 WSL `bash.exe` 别名永远无法遮蔽真实 Git Bash(本工具是 MSYS 而非 WSL)。
18
19
  - 沙箱 `auto`:Git Bash 探测 windows-acl runner,WSL 探测发行版内 `bwrap`;探测失败如实降级为无限制运行并如实报告。显式 `sandbox: bwrap` 而发行版缺少 bubblewrap 时,在首次执行 `wsl_bash` 命令时响亮报错(不会拖垮启动),其余后端不受影响。
@@ -30,7 +31,7 @@
30
31
 
31
32
  > ⚠️ **`git_bash` 在 Git for Windows 部署下通常无法沙箱化。** windows-acl runner 以受限令牌拉起 MSYS `bash.exe` 时 `CreateProcessAsUserW` 返回 Win32 error 2(`cmd.exe`、`pwsh.exe` 均可正常拉起);`sandbox: auto` 的探针失败后按契约降级为**无限制运行**。**不要假设 `git_bash` 受 DSH 沙箱保护**——敏感操作请改用 `pwsh`(受限令牌生效)或 `wsl_bash`(bwrap 生效),或走显式升级审批。
32
33
  >
33
- > ⚠️ **`wsl_bash` 的沙箱依赖发行版内的 bubblewrap。** 未安装 bwrap 时 `auto` 同样降级为无限制运行;探针结果在**宿主进程生命周期内缓存**——安装 bwrap 后必须重启 `dsh web`(或改动 shell 设置节触发后端重建)才会重新探测。
34
+ > ⚠️ **`wsl_bash` 的沙箱依赖发行版内的 bubblewrap。** 未安装 bwrap 时 `auto` 同样降级为无限制运行;探针结果在**宿主进程生命周期内缓存**——安装 bwrap 后必须重启 `dsh web`(或编辑 profile patch 触发工具行重载)才会重新探测。
34
35
  >
35
36
  > ⚠️ **拒绝判定要求命令以非零退出结束。** 被拦截的写操作若以成功命令收尾(如 `echo nope > /etc/x; echo done`),整体退出码为 0,不会标记 `[sandbox: file access denied]`(与上游 bash-sandbox 的判定规则一致,避免误报)。
36
37
  >
@@ -38,12 +39,17 @@
38
39
  > **`requireSandbox`:探针失败时拒绝无沙箱运行(可选强化)。** 两个后端均支持 `requireSandbox: true`(默认 `false`,保持既有降级行为)。开启后,探针失败(git-bash 的 windows-acl 不可用 / wsl-bash 缺少 bwrap)时:`danger-full-access` 模式下照常放行(无沙箱运行等价于显式全权批准),`read-only` / `workspace-write` 模式下**拒绝执行**并报错,提示修复沙箱或升级到 `danger-full-access`。同时工具层会声明沙箱并开放 `sandbox_permissions` 升级参数,使模型可以走审批升级。示例:
39
40
 
40
41
  > ```yaml
41
- > # cordis.patch.yml 的 win-mb-shell-select 行
42
- > config:
43
- > backends: [git-bash, wsl-bash, pwsh]
44
- > default: pwsh
45
- > gitBash: { requireSandbox: true }
46
- > wslBash: { requireSandbox: true }
42
+ > # cordis.patch.yml 的 win-mb-tool-git / win-mb-tool-wsl 行:
43
+ > # 每个工具行只带自己后端的配置分区
44
+ > - id: win-mb-tool-git
45
+ > name: 'dsh-win-multi-bash/tool-git-bash'
46
+ > config:
47
+ > gitBash: { requireSandbox: true }
48
+ >
49
+ > - id: win-mb-tool-wsl
50
+ > name: 'dsh-win-multi-bash/tool-wsl-bash'
51
+ > config:
52
+ > wslBash: { requireSandbox: true }
47
53
  > ```
48
54
 
49
55
  > 注意:`requireSandbox` 与 `sandbox: none` 互斥使用——显式 `none` 是用户主动放弃沙箱,保持放行;`requireSandbox` 只管「想沙箱但探针失败」的情形。
@@ -60,11 +66,13 @@ wsl.exe -d Ubuntu-24.04 -e bash -c "command -v bwrap && bwrap --version" # 验
60
66
  - 探针探测的是 `wsl -l -q` 的**第一个发行版**;若目标发行版不是第一个,在 `cordis.patch.yml` 的 `wslBash.wslDistro` 钉定它,并**在该发行版内**安装 bwrap(如 `Ubuntu-24.04`;`docker-desktop` 无 bash,不可用)。
61
67
  - `sudo` 可能需要密码(取决于发行版的 sudoers 配置);脚本化请用 `apt-get install -y`。
62
68
  - 其它发行版系:Fedora `dnf install bubblewrap`,Alpine `apk add bubblewrap`。
63
- - 装完后**必须重启 `dsh web`**(或改动 shell 设置节触发后端重建)——探针结果在宿主进程生命周期内缓存,重启前 `wsl_bash` 仍按无沙箱运行。
69
+ - 装完后**必须重启 `dsh web`**(或编辑 profile patch 触发工具行重载)——探针结果在宿主进程生命周期内缓存,重启前 `wsl_bash` 仍按无沙箱运行。
64
70
 
65
71
  ## 工具提示词(面向模型的描述)
66
72
 
67
- `git_bash` / `wsl_bash` 的工具描述刻意保持精简,与官方 `tool-pwsh` 同构:每次调用全新 shell、方言的路径/环境变量写法、`[exit code: N]` 标记、`$DSH_*` 环境事实、沙箱行为、输出截断、后台任务与升级契约。更长的方言说明(MSYS 路径改写、WSL base64 载荷)放在本文档而不是模型可见的描述里。
73
+ `git_bash` / `wsl_bash` 的工具描述刻意保持精简,与官方 `tool-pwsh` 同构:每次调用全新 shell、方言的路径/环境变量写法、`[exit code: N]` 标记、`$DSH_*` 环境事实、沙箱行为、输出截断、删除/移动前的目标路径校验、未设变量的 `${VAR:?}` 兜底、后台任务与升级契约。更长的方言说明(MSYS 路径改写、WSL base64 载荷)放在本文档而不是模型可见的描述里。
74
+
75
+ 这两个工具都用 `bash -c`,因此上述安全提示采用 `tool-bash` 的 bash 版措辞而非 `tool-pwsh` 的:bash 里 `$HOME` 是可赋值的普通变量,pwsh 的「不要给自动变量赋值」那句在此会误导;bash 对计算路径的兜底就是上面的 `${VAR:?}` 写法——它让未设变量直接报错,而不是静默展开成空串。
68
76
 
69
77
  `git_bash` 的描述还带一条路径格式提示:
70
78
 
@@ -72,6 +80,10 @@ wsl.exe -d Ubuntu-24.04 -e bash -c "command -v bwrap && bwrap --version" # 验
72
80
 
73
81
  即命令输出里的 MSYS 路径(如 `/d/WorkSpace/foo`)在交给 dsh 文件工具前要转成 Windows 形式(`D:\WorkSpace\foo`);而在 bash 命令内部,MSYS 路径才是 shell 期望的写法。
74
82
 
83
+ **`workdir` 接受原生、MSYS 与 WSL 挂载三种写法。** 模型在被告知方言路径是 MSYS/WSL 形式后,自然会用同样的形式写 `workdir`;解析器因此把单字母盘符形式(`/c/...`)与 WSL 挂载形式(`/mnt/c/...`)在交给 `spawn` 之前转成原生 `C:\...`,而 MSYS 的 POSIX 根(`/etc`、`/usr`、`/tmp`)与发行版侧路径(如 `/mnt/data`)保持原样,相对路径仍相对会话工作区解析。在此转换之前,MSYS 形式的 `workdir` 会以 `spawn <shell> ENOENT` 失败——一个「找不到 shell」的假象,实际是 cwd 不可用。
84
+
85
+ 工具行还会注册一段系统提示词小节(`tool:<name>`):每次结果都要核对 `[exit code: N]` 标记,且依赖前一步的后续命令要用 `&&` 或 `set -o pipefail` 串接——`;` 不会因失败中止,而 `cmd | tail` 返回的是 `tail` 的状态而非 `cmd` 的。这属于「如何组合多步命令」的跨调用指导,因此放在提示词里而不是单次调用的 schema 里;它也让运行时自带的截尾能力成为「不必用管道限制输出」的理由。
86
+
75
87
  ## 路径转换(MSYS 自动改写)
76
88
 
77
89
  Git Bash 在调用原生 Windows 程序时会把形如 `/root` 的 POSIX 路径自动改写成 Windows 路径(如 `<Git 根目录>\root`),这是 MSYS 的标准行为,不是本插件的缺陷。在 `git_bash` 里直接调用 `wsl.exe`(或其他原生 exe)并传 POSIX 路径时会被改写而失败:
@@ -91,14 +103,14 @@ MSYS_NO_PATHCONV=1 wsl.exe -e ls /root # ✓ 原样传递
91
103
 
92
104
  ```
93
105
  lib/
94
- ├── shell-select/ ShellSelectExecutor(ctx.shell 选择器)
95
106
  ├── bash-git/ GitBashExecutor(MSYS)
96
107
  ├── bash-wsl/ WslBashExecutor(WSL,base64 载荷)
97
- ├── tool-bash/ 工具工厂 + git_bash / wsl_bash 实例
108
+ ├── tool-bash/ 工具工厂 + git_bash / wsl_bash 实例、
109
+ │ 后端持有逻辑(types/backend.js)
98
110
  └── vendor/ 运行器失败分类与 bwrap 配置辅助模块
99
111
  ```
100
112
 
101
- 若部署的 base bundle 已自带 `shell-select` 行,插件的 patch 会禁用该行、由本插件选择器占据席位(两个提供者会冲突);基座没有该行时此条目是无害 no-op。
113
+ 由于本插件没有任何行注册为 `ctx.shell`,patch 只插入自己的两个工具行、不禁用任何行:基座自身的 shell 接线(Windows 上是 `pwsh-sandbox`,其他平台是 `bash-sandbox`)保持原样,pwsh 无论本插件是否加载都正常工作。
102
114
 
103
115
  ## 前置条件
104
116
 
@@ -141,7 +153,13 @@ dsh plugin --profile web remove dsh-win-multi-bash
141
153
  powershell -ExecutionPolicy Bypass -File .\smoke\run.ps1
142
154
  ```
143
155
 
144
- 在 profile 运行时上 boot 真实组合(不修改任何 profile),验证 `git_bash` / `wsl_bash` 注册并真实执行,含显式 `bashPath` 变体;需要 node >= 20。
156
+ 在 profile 运行时上 boot 真实组合(不修改任何 profile),验证 `git_bash` / `wsl_bash` 注册并真实执行(含显式 `bashPath` 变体),并断言 `ctx.shell` 仍由基座自带的行提供——这正是本插件曾经弄坏的那一点。需要 node >= 20。
157
+
158
+ ```powershell
159
+ powershell -ExecutionPolicy Bypass -File .\smoke\audit.ps1
160
+ ```
161
+
162
+ 改为运行完整审计套件:vendor 辅助模块、executor 内部与工具配置 schema 的纯单元覆盖,外加真实 `cordis.patch.yml` 与各类错误配置的 boot 集成矩阵。
145
163
 
146
164
  ## 故障排查
147
165
 
@@ -155,14 +173,14 @@ powershell -ExecutionPolicy Bypass -File .\smoke\run.ps1
155
173
  | `wsl_bash` 报 `bwrap was not found` | 已配置 `sandbox: bwrap` 但发行版内没有 bubblewrap:按上方「沙箱行为 → 为 `wsl_bash` 启用 bwrap 沙箱」安装(`sudo apt-get install -y bubblewrap`)并重启 `dsh web`,或改用 `sandbox: auto` / `none` |
156
174
  | `wsl_bash` 沙箱报 bwrap runner 失败 | bwrap 的工作区根取 Windows 盘符路径的 Linux 侧(`/mnt/<盘符>/...`):UNC 工作区根会响亮报错;发行版自定义了 automount 根(wsl.conf `automount.root`)时需要相应配置 |
157
175
  | `git_bash` 里调 `wsl.exe` 等原生程序传 POSIX 路径报 `No such file or directory` | MSYS 把 `/root` 等改写成 `<Git 根目录>\root`:加 `MSYS_NO_PATHCONV=1` / `MSYS2_ARG_CONV_EXCL="*"`,或用 `//` 前缀;WSL 操作直接改用 `wsl_bash` 工具 |
158
- | 安装 bubblewrap 后 `wsl_bash` 仍无沙箱 | bwrap 探针结果在宿主进程生命周期内缓存:重启 `dsh web`,或改动 shell 设置节触发后端重建后再试 |
159
- | 执行报 `shell-select: backend "x" is not enabled` | backends 列表与工具名不匹配;保持 `backends: [git-bash, wsl-bash, pwsh]` |
176
+ | 安装 bubblewrap 后 `wsl_bash` 仍无沙箱 | bwrap 探针结果在宿主进程生命周期内缓存:重启 `dsh web`,或编辑 profile patch 触发工具行重载后再试 |
177
+ | 启动告警 `win-mb-tool-git … waiting for service: shell`(或 dsh 自带的 `tool-pwsh` / `permission-presets` 如此) | 没有任何行提供 `ctx.shell`。本插件已不提供该席位:检查基座的 `pwsh-sandbox` 行是否被别的 patch 禁用了 |
160
178
 
161
179
  ## 文件布局
162
180
 
163
181
  ```
164
182
  dsh-win-multi-bash/
165
- ├── package.json # dsh.bundle 清单;exports 暴露 ./shell-select ./tool-git-bash ./tool-wsl-bash
183
+ ├── package.json # dsh.bundle 清单;exports 暴露 ./tool-git-bash ./tool-wsl-bash
166
184
  ├── cordis.patch.yml # 组合接线(即文档)
167
185
  ├── install.ps1 # 方式 A 热插(junction + managed 块 + Git Bash 检测)
168
186
  ├── uninstall.ps1 # 方式 A 热拔
@@ -33,11 +33,10 @@ from; import specifiers were rewired to package-local paths where noted.
33
33
 
34
34
  | Plugin path | Upstream package | Notes |
35
35
  | --- | --- | --- |
36
- | `lib/shell-select/` | `packages/shell/shell-select` | `ShellSelectExecutor`; imports of `@deepseek-ai/dsh-bash-git` / `dsh-bash-wsl` rewritten to `../bash-git/index.js` / `../bash-wsl/index.js` |
37
36
  | `lib/bash-git/` | `packages/shell/bash-git` | `GitBashExecutor`; `@deepseek-ai/dsh-bash-sandbox/helpers` import rewritten to `../vendor/helpers.js` |
38
37
  | `lib/bash-wsl/` | `packages/shell/bash-wsl` | `WslBashExecutor`; helpers and `@deepseek-ai/dsh-sandbox-local/profiles` imports rewritten to `../vendor/*.js` |
39
- | `lib/tool-bash/` | `packages/shell/tool-bash` | `defineShellTool` factory + `git_bash` / `wsl_bash` instances |
40
- | `lib/vendor/helpers.js` | `packages/shell/bash-sandbox/lib/types/helpers.js` | Self-contained (node:fs only) |
38
+ | `lib/tool-bash/` | `packages/shell/tool-bash` | `defineShellTool` factory + `git_bash` / `wsl_bash` instances. `types/backend.js` is plugin-original, not upstream: it owns an executor per tool, replacing the upstream `packages/shell/shell-select` module this plugin used to bundle (dsh 0.1.7 removed `ShellExecRequest.shell`, the routing field a selector dispatches on) |
39
+ | `lib/vendor/helpers.js` | `packages/shell/bash-sandbox/lib/types/helpers.js` | Self-contained (node:fs only). Retains the `anchored` runner-failure rule the base runtime's `@deepseek-ai/dsh-sandbox` does not export (the bundled bwrap rules depend on it) |
41
40
  | `lib/vendor/bwrap-profiles.js` | `packages/sandbox/sandbox-local/lib/types/profiles.js` | Extracted `BWRAP_RUNNER_FAILURE_RULES` + `bwrapProfileArgs` only (drops the landlock native-addon import) |
42
41
 
43
42
  The bundled files retain their upstream `@module` documentation headers.
package/cordis.patch.yml CHANGED
@@ -1,70 +1,64 @@
1
1
  # dsh-win-multi-bash — self-contained Windows multi-bash wiring.
2
2
  #
3
- # This patch is the whole plugin: it disables the base shell rows that would
4
- # fight for the single ctx.shell seat, then inserts the plugin's own rows.
5
- # Every inserted row loads code from THIS package (exports ./shell-select,
6
- # ./tool-git-bash, ./tool-wsl-bash) — nothing depends on runtime packages
7
- # beyond the published @deepseek-ai base packages (dsh-shell, dsh-sandbox,
8
- # dsh-pwsh-sandbox, dsh-tools, ...).
3
+ # This patch is the whole plugin: it inserts the plugin's own rows. Every
4
+ # inserted row loads code from THIS package (exports ./tool-git-bash,
5
+ # ./tool-wsl-bash) — nothing depends on runtime packages beyond the published
6
+ # @deepseek-ai base packages (dsh-bash-local, dsh-sandbox, dsh-tools,
7
+ # dsh-shell, dsh-llm, ...).
9
8
  #
10
9
  # ── What the rows do ─────────────────────────────────────────────────────────
11
- # pwsh-sandbox — disabled: its pwsh backend is held by our selector's
12
- # `pwsh:` partition (idempotent when the base bundle
13
- # already disables it).
14
- # shell-select — disabled when the base bundle already mounts its own
15
- # selector: two providers would fight for
16
- # the ctx.shell seat. No-op on base bundles without it.
17
- # win-mb-shell-select — our bundled selector: routes
18
- # `request.shell ?? default` across git-bash / wsl-bash
19
- # / pwsh backends; pwsh stays the default.
20
10
  # win-mb-tool-git — registers the model-facing `git_bash` tool
21
- # (request.shell 'git-bash', MSYS dialect) into the
22
- # host tools registry: every session sees it regardless
23
- # of its agent preset. Its model-facing description
24
- # mirrors the official tool-pwsh skeleton (concise)
25
- # and reminds the model that MSYS paths are valid only
26
- # inside Git Bash — dsh's file tools on Windows take
27
- # native C:\... paths.
28
- # win-mb-tool-wsl — the `wsl_bash` tool (request.shell 'wsl-bash');
11
+ # (MSYS dialect) into the host tools registry: every
12
+ # session sees it regardless of its agent preset. The
13
+ # tool owns its own GitBashExecutor; its model-facing
14
+ # description mirrors the official tool-pwsh skeleton
15
+ # (concise) and reminds the model that MSYS paths are
16
+ # valid only inside Git Bash — dsh's file tools on
17
+ # Windows take native C:\... paths.
18
+ # win-mb-tool-wsl — the `wsl_bash` tool, owning its own WslBashExecutor;
29
19
  # its description mirrors tool-pwsh's concise skeleton.
30
20
  #
31
- # Both tool rows and the selector are win32-only; POSIX keeps the direct
32
- # bash-sandbox seat untouched.
33
-
34
- - id: pwsh-sandbox
35
- disabled: true
36
-
37
- - id: shell-select
38
- disabled: true
21
+ # Both rows are win32-only; POSIX keeps the base bundle's own bash wiring.
22
+ #
23
+ # ── Why nothing here touches the ctx.shell seat ──────────────────────────────
24
+ # dsh 0.1.7 removed the shell seam's routing field (`ShellExecRequest.shell`),
25
+ # which is what the pre-0.1.7 design used: one selector executor occupying
26
+ # `ctx.shell` and dispatching each request to a backend by name. With no
27
+ # routing input there is nothing for a selector to select on — the tool is
28
+ # what knows which shell it wants — so each of our tools now owns exactly one
29
+ # executor and drives it directly, and `ctx.shell` is left entirely to the
30
+ # base bundle's own row (`pwsh-sandbox` on win32, `bash-sandbox` elsewhere).
31
+ #
32
+ # That is also why this patch no longer disables `pwsh-sandbox`: the base
33
+ # bundle's pwsh executor is what keeps `ctx.shell` (and therefore dsh's own
34
+ # `tool-pwsh` and `permission-presets`, which inject `shell`) alive. pwsh
35
+ # remains the default shell for the composition; it is simply served by dsh
36
+ # itself rather than by us.
39
37
 
40
38
  - insert:
41
- - id: win-mb-shell-select
42
- name: 'dsh-win-multi-bash/shell-select'
43
- disabled: !!js process.platform !== 'win32'
44
- config:
45
- backends: [git-bash, wsl-bash, pwsh]
46
- default: pwsh
47
- # Optional per-machine executable pins (restate the whole config when
48
- # uncommenting — a patch replaces the row's entire config). The values
49
- # below are placeholders only — omit the pins entirely and let
50
- # resolution probe automatically (git.exe layout inference → well-known
51
- # Program Files on every fixed drive → PATH, with the WSL launcher and
52
- # WindowsApps aliases excluded):
53
- # gitBash:
54
- # bashPath: '<Git 安装目录>\usr\bin\bash.exe'
55
- # wslBash:
56
- # wslDistro: '<发行版名,如 Ubuntu-24.04>'
57
- #
58
- # Optional sandbox hardening: when a backend's probe fails (windows-acl
59
- # cannot confine MSYS bash / bwrap missing in the distro), refuse to run
60
- # unconfined unless the effective mode is danger-full-access:
61
- # gitBash: { requireSandbox: true }
62
- # wslBash: { requireSandbox: true }
63
-
64
39
  - id: win-mb-tool-git
65
40
  name: 'dsh-win-multi-bash/tool-git-bash'
66
41
  disabled: !!js process.platform !== 'win32'
42
+ # Optional per-machine executable pin (restate the whole partition when
43
+ # uncommenting — a patch replaces the row's entire config). The value
44
+ # below is a placeholder only — omit the pin entirely and let resolution
45
+ # probe automatically (git.exe layout inference → well-known Program
46
+ # Files on every fixed drive → PATH, with the WSL launcher and
47
+ # WindowsApps aliases excluded):
48
+ # gitBash:
49
+ # bashPath: '<Git 安装目录>\usr\bin\bash.exe'
50
+ #
51
+ # Optional sandbox hardening: when the windows-acl probe fails (it cannot
52
+ # confine MSYS bash), refuse to run unconfined unless the effective mode
53
+ # is danger-full-access:
54
+ # gitBash:
55
+ # requireSandbox: true
67
56
 
68
57
  - id: win-mb-tool-wsl
69
58
  name: 'dsh-win-multi-bash/tool-wsl-bash'
70
59
  disabled: !!js process.platform !== 'win32'
60
+ # Optional per-machine pins and hardening, same shape as the git row:
61
+ # wslBash:
62
+ # wslDistro: '<发行版名,如 Ubuntu-24.04>'
63
+ # wslPath: 'C:\Windows\System32\wsl.exe'
64
+ # requireSandbox: true