dsh-win-multi-bash 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -4,5 +4,5 @@
4
4
  # along and re-record with the harness docs tooling (not shipped with this
5
5
  # package):
6
6
  # node scripts/verify-docs.mjs --write <dir> # run from the deepseek-harness checkout
7
- README.md: 2511078d6aae9388bfcf5047ef64746796717d80
8
- README.zh.md: 976e708f2c1451006117826ca136ed46529d3f30
7
+ README.md: 498a99ec73bd8a0015e2737d54696eb04d0989d6
8
+ README.zh.md: 7fcc09ebfab371d4236645737710808cbf22c7c9
package/README.md CHANGED
@@ -14,7 +14,7 @@ A Windows multi-bash plugin for DeepSeek Harness: `git_bash` / `wsl_bash` model
14
14
 
15
15
  - `shell-select` occupies the single `ctx.shell` seat and routes `request.shell ?? default` to one backend; `default` stays `pwsh`.
16
16
  - 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
- - Git Bash is found automatically, in order: an explicit `gitBash.bashPath`, the well-known Program Files locations, `bash.exe` on PATH (the Windows WSL launcher `System32\bash.exe` is **never** selected — this tool is MSYS, not WSL), Git install roots inferred from `git.exe` layout directories on PATH (so an install reachable through `git` is found without a pin), and finally the `HKLM\SOFTWARE\GitForWindows` install path (which the Git for Windows installer always records, covering custom-drive and portable installs).
17
+ - 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
18
  - 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.
19
19
  - All rows register host-plane: every session sees the tools regardless of its agent preset.
20
20
 
@@ -62,6 +62,16 @@ wsl.exe -d Ubuntu-24.04 -e bash -c "command -v bwrap && bwrap --version" # ver
62
62
  - Other distro families: Fedora `dnf install bubblewrap`, Alpine `apk add bubblewrap`.
63
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.
64
64
 
65
+ ## Tool prompts (model-facing descriptions)
66
+
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.
68
+
69
+ `git_bash`'s description additionally carries a path-format hint:
70
+
71
+ > MSYS paths work inside Git Bash only — dsh's file tools (`read`, `write`, `edit`) on Windows take native `C:\...` paths.
72
+
73
+ 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
+
65
75
  ## Path conversion (MSYS auto-rewriting)
66
76
 
67
77
  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:
@@ -140,7 +150,7 @@ Boots a real composition over the profile runtime (modifying nothing), verifies
140
150
  | New sessions lack `git_bash` / `wsl_bash` | Check the managed block exists in the profile patch, the profile `node_modules/dsh-win-multi-bash` junction exists, and the package-local `node_modules/@deepseek-ai` junction exists (re-run install.ps1); confirm the running `dsh web` hot-reloads the profile patch |
141
151
  | Boot fails with `duplicate loader entry id` | Both plug paths are active; remove one of them |
142
152
  | Boot fails with `Cannot find package '@deepseek-ai/...'` | The package-local `node_modules/@deepseek-ai` junction is missing (re-run install.ps1), or the profile runtime lacks the base packages |
143
- | `git_bash` reports bash not found | Git Bash is outside the default probe paths: re-run install.ps1 (registry auto-detect) or set `gitBash.bashPath` manually |
153
+ | `git_bash` reports bash not found (or spawns the WSL `WindowsApps\bash.exe` alias with `spawn ... ENOENT`) | Git Bash is outside the probe paths, or a stale WSL app-execution alias shadows resolution: delete `%LOCALAPPDATA%\Microsoft\WindowsApps\bash.exe` (Settings → Apps → Advanced app settings → App execution aliases), re-run install.ps1 (registry auto-detect), or set `gitBash.bashPath` manually |
144
154
  | `wsl_bash` errors | Check `wsl.exe --status` for a default distro; set `wslBash.wslDistro` to name one |
145
155
  | `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` |
146
156
  | `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 |
package/README.zh.md CHANGED
@@ -14,7 +14,7 @@
14
14
 
15
15
  - `shell-select` 占据唯一的 `ctx.shell` 席位,按 `request.shell ?? default` 路由;`default` 保持 `pwsh`。
16
16
  - 可执行文件解析与沙箱探测全部惰性化:未安装 Git Bash / WSL 不影响 pwsh,首次使用时才响亮报错。
17
- - Git Bash 自动查找,顺序为:显式 `gitBash.bashPath` → 常见 Program Files 位置 → PATH 上的 `bash.exe`(**绝不选** Windows 的 WSL 启动器 `System32\bash.exe`——本工具是 MSYS 而非 WSL)→ 从 PATH 上 `git.exe` 布局目录反推的 Git 安装根(因此通过 `git` 可达的安装无需钉定即可找到)→ 最后读取 `HKLM\SOFTWARE\GitForWindows` 注册表安装路径(Git for Windows 安装器必写该键,覆盖自定义盘符与便携安装)。
17
+ - 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
18
  - 沙箱 `auto`:Git Bash 探测 windows-acl runner,WSL 探测发行版内 `bwrap`;探测失败如实降级为无限制运行并如实报告。显式 `sandbox: bwrap` 而发行版缺少 bubblewrap 时,在首次执行 `wsl_bash` 命令时响亮报错(不会拖垮启动),其余后端不受影响。
19
19
  - 所有行在 host 平面注册:无论会话使用哪个 agent preset,都能看到这两个工具。
20
20
 
@@ -62,6 +62,16 @@ wsl.exe -d Ubuntu-24.04 -e bash -c "command -v bwrap && bwrap --version" # 验
62
62
  - 其它发行版系:Fedora `dnf install bubblewrap`,Alpine `apk add bubblewrap`。
63
63
  - 装完后**必须重启 `dsh web`**(或改动 shell 设置节触发后端重建)——探针结果在宿主进程生命周期内缓存,重启前 `wsl_bash` 仍按无沙箱运行。
64
64
 
65
+ ## 工具提示词(面向模型的描述)
66
+
67
+ `git_bash` / `wsl_bash` 的工具描述刻意保持精简,与官方 `tool-pwsh` 同构:每次调用全新 shell、方言的路径/环境变量写法、`[exit code: N]` 标记、`$DSH_*` 环境事实、沙箱行为、输出截断、后台任务与升级契约。更长的方言说明(MSYS 路径改写、WSL base64 载荷)放在本文档而不是模型可见的描述里。
68
+
69
+ `git_bash` 的描述还带一条路径格式提示:
70
+
71
+ > MSYS paths work inside Git Bash only — dsh's file tools (`read`, `write`, `edit`) on Windows take native `C:\...` paths.
72
+
73
+ 即命令输出里的 MSYS 路径(如 `/d/WorkSpace/foo`)在交给 dsh 文件工具前要转成 Windows 形式(`D:\WorkSpace\foo`);而在 bash 命令内部,MSYS 路径才是 shell 期望的写法。
74
+
65
75
  ## 路径转换(MSYS 自动改写)
66
76
 
67
77
  Git Bash 在调用原生 Windows 程序时会把形如 `/root` 的 POSIX 路径自动改写成 Windows 路径(如 `<Git 根目录>\root`),这是 MSYS 的标准行为,不是本插件的缺陷。在 `git_bash` 里直接调用 `wsl.exe`(或其他原生 exe)并传 POSIX 路径时会被改写而失败:
@@ -140,7 +150,7 @@ powershell -ExecutionPolicy Bypass -File .\smoke\run.ps1
140
150
  | 新会话看不到 `git_bash` / `wsl_bash` | 检查 profile patch 里 managed 块存在、profile `node_modules/dsh-win-multi-bash` junction 存在、包内 `node_modules/@deepseek-ai` junction 存在(重跑 install.ps1);确认运行中 `dsh web` 热重载生效 |
141
151
  | 引导失败 `duplicate loader entry id` | 两种插拔方式混用了;先卸载其中一种 |
142
152
  | 引导失败 `Cannot find package '@deepseek-ai/...'` | 包内 `node_modules/@deepseek-ai` junction 缺失(重跑 install.ps1),或 profile 运行时基础包不完整 |
143
- | `git_bash` 执行报找不到 bash | Git Bash 不在默认探测路径:重跑 install.ps1(注册表自动检测),或手动设置 `gitBash.bashPath` |
153
+ | `git_bash` 执行报找不到 bash(或去 spawn WSL 的 `WindowsApps\bash.exe` 别名并报 `spawn ... ENOENT`) | Git Bash 不在探测路径,或失效的 WSL 应用执行别名遮蔽了解析:删除 `%LOCALAPPDATA%\Microsoft\WindowsApps\bash.exe`(设置 → 应用 → 高级应用设置 → 应用执行别名),重跑 install.ps1(注册表自动检测),或手动设置 `gitBash.bashPath` |
144
154
  | `wsl_bash` 执行报错 | `wsl.exe --status` 是否有默认发行版;可在 `wslBash.wslDistro` 指定发行版名 |
145
155
  | `wsl_bash` 报 `bwrap was not found` | 已配置 `sandbox: bwrap` 但发行版内没有 bubblewrap:按上方「沙箱行为 → 为 `wsl_bash` 启用 bwrap 沙箱」安装(`sudo apt-get install -y bubblewrap`)并重启 `dsh web`,或改用 `sandbox: auto` / `none` |
146
156
  | `wsl_bash` 沙箱报 bwrap runner 失败 | bwrap 的工作区根取 Windows 盘符路径的 Linux 侧(`/mnt/<盘符>/...`):UNC 工作区根会响亮报错;发行版自定义了 automount 根(wsl.conf `automount.root`)时需要相应配置 |
package/cordis.patch.yml CHANGED
@@ -20,8 +20,13 @@
20
20
  # win-mb-tool-git — registers the model-facing `git_bash` tool
21
21
  # (request.shell 'git-bash', MSYS dialect) into the
22
22
  # host tools registry: every session sees it regardless
23
- # of its agent preset.
24
- # win-mb-tool-wsl — the `wsl_bash` tool (request.shell 'wsl-bash').
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');
29
+ # its description mirrors tool-pwsh's concise skeleton.
25
30
  #
26
31
  # Both tool rows and the selector are win32-only; POSIX keeps the direct
27
32
  # bash-sandbox seat untouched.
@@ -42,8 +47,9 @@
42
47
  # Optional per-machine executable pins (restate the whole config when
43
48
  # uncommenting — a patch replaces the row's entire config). The values
44
49
  # below are placeholders only — omit the pins entirely and let
45
- # resolution probe automatically (well-known locations → PATH, the WSL
46
- # launcher excluded, git.exe layout inference):
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):
47
53
  # gitBash:
48
54
  # bashPath: '<Git 安装目录>\usr\bin\bash.exe'
49
55
  # wslBash:
@@ -2,7 +2,7 @@ import { spawnSync } from "node:child_process";
2
2
  import z from "@deepseek-ai/schemastery";
3
3
  import { LocalBashExecutor } from "@deepseek-ai/dsh-bash-local";
4
4
  import { classifyDenial, classifyRunnerFailure, isRunnerSpawnFailure, matchesSignature } from "../vendor/helpers.js";
5
- import { lstatSync } from "node:fs";
5
+ import { existsSync, lstatSync } from "node:fs";
6
6
  import { basename, dirname, join } from "node:path";
7
7
  /**
8
8
  * Derive a config schema from a base object schema WITHOUT mutating it.
@@ -86,44 +86,95 @@ function gitRootCandidates(env = process.env) {
86
86
  return roots;
87
87
  }
88
88
  /**
89
- * Well-known Git for Windows install locations plus PATH entries. The Windows
90
- * WSL launcher (`System32\bash.exe`) is deliberately excluded: this executor is
91
- * MSYS Git Bash, and silently routing it into WSL would make a non-WSL tool
92
- * depend on WSL — resolution then either finds a real Git Bash or fails loud
93
- * with the `bashPath` hint. PATH entries shaped like Git layout directories
94
- * with a real `git.exe` additionally contribute their `<root>\usr\bin` and
95
- * `<root>\bin` bash candidates, so an install reachable through `git` on PATH
96
- * is found automatically.
89
+ * The well-known Git for Windows layout under one Program Files root
90
+ * (`<root>\Git\bin\bash.exe` and `<root>\Git\usr\bin\bash.exe`). Shared by the
91
+ * env `ProgramFiles` probes and the per-drive probes so an install on a
92
+ * non-C: drive (e.g. `D:\Program Files\Git`) is found without a pin.
93
+ * @param programFilesRoot - one Program Files root (e.g. `C:\Program Files`).
94
+ * @returns the two layout-shaped bash candidates under that root.
95
+ */
96
+ function gitCandidatesUnder(programFilesRoot) {
97
+ return [
98
+ join(programFilesRoot, "Git", "bin", "bash.exe"),
99
+ join(programFilesRoot, "Git", "usr", "bin", "bash.exe")
100
+ ];
101
+ }
102
+ /**
103
+ * The drive letters to probe for the well-known Git layout. An explicit
104
+ * `GitProbeDrives` env value (comma/space/`;`-separated letters) replaces the
105
+ * default of every existing fixed drive; the empty string disables drive
106
+ * probing entirely. Test fixtures use that override so resolution stays a pure
107
+ * function of the injected env.
108
+ * @param env - the environment to probe; defaults to the process environment.
109
+ * @returns existing drive roots in probe order (e.g. `C:\`, `D:\`).
110
+ */
111
+ function probeDrives(env = process.env) {
112
+ const override = env.GitProbeDrives;
113
+ const letters = override !== void 0 && override.trim().length > 0
114
+ ? override.split(/[,;\s]+/).map((s) => s.trim().replace(/:$/, "")).filter((s) => s.length > 0)
115
+ : override !== void 0 ? [] : "ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("");
116
+ const drives = [];
117
+ for (const letter of letters) {
118
+ const root = `${letter.charAt(0).toUpperCase()}:\\`;
119
+ if (existsSync(root)) drives.push(root);
120
+ }
121
+ return drives;
122
+ }
123
+ /**
124
+ * Git Bash candidate paths in resolution order: Git roots inferred from PATH
125
+ * `git.exe` layout dirs first (the strongest signal — a real install, however
126
+ * it got on PATH), then the well-known Program Files layout under every fixed
127
+ * drive (covers non-C: installs), then every PATH entry. The Windows WSL
128
+ * launcher (`System32\bash.exe`) and the `WindowsApps` alias directories are
129
+ * deliberately excluded, and candidates are only accepted when they are real
130
+ * regular files (symlinks/reparse points rejected), so a stale WSL
131
+ * app-execution alias can never shadow a real Git Bash. PATH entries shaped
132
+ * like Git layout directories with a real `git.exe` additionally contribute
133
+ * their `<root>\usr\bin` and `<root>\bin` bash candidates via the first step,
134
+ * so an install reachable through `git` on PATH is found automatically.
97
135
  * @param env - the environment to probe; defaults to the process environment.
98
136
  * @returns candidate `bash.exe` paths in resolution order.
99
137
  */
100
138
  function candidateBashPaths(env = process.env) {
101
- const programFiles = env.ProgramFiles ?? "C:\\Program Files";
102
- const programFilesX86 = env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)";
103
139
  const wslLauncher = join(env.SystemRoot ?? "C:\\Windows", "System32", "bash.exe").toLowerCase();
104
- const candidates = [
105
- join(programFiles, "Git", "bin", "bash.exe"),
106
- join(programFiles, "Git", "usr", "bin", "bash.exe"),
107
- join(programFilesX86, "Git", "bin", "bash.exe")
108
- ];
140
+ const candidates = [];
141
+ for (const root of gitRootCandidates(env)) {
142
+ for (const candidate of [join(root, "usr", "bin", "bash.exe"), join(root, "bin", "bash.exe")]) {
143
+ if (!candidates.includes(candidate)) candidates.push(candidate);
144
+ }
145
+ }
146
+ for (const pf of [env.ProgramFiles ?? "C:\\Program Files", env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)"]) {
147
+ for (const candidate of gitCandidatesUnder(pf)) {
148
+ if (!candidates.includes(candidate)) candidates.push(candidate);
149
+ }
150
+ }
151
+ for (const drive of probeDrives(env)) {
152
+ for (const candidate of gitCandidatesUnder(join(drive, "Program Files"))) {
153
+ if (!candidates.includes(candidate)) candidates.push(candidate);
154
+ }
155
+ }
109
156
  for (const entry of (env.PATH ?? "").split(";")) {
110
157
  const trimmed = entry.trim().replace(/^"|"$/g, "");
111
158
  if (trimmed.length === 0) continue;
112
159
  const candidate = join(trimmed, "bash.exe");
113
160
  if (candidate.toLowerCase() === wslLauncher) continue;
114
- candidates.push(candidate);
115
- }
116
- for (const root of gitRootCandidates(env)) {
117
- for (const candidate of [join(root, "usr", "bin", "bash.exe"), join(root, "bin", "bash.exe")]) {
118
- if (!candidates.includes(candidate)) candidates.push(candidate);
119
- }
161
+ if (basename(trimmed).toLowerCase() === "windowsapps") continue;
162
+ if (!candidates.includes(candidate)) candidates.push(candidate);
120
163
  }
121
164
  return candidates;
122
165
  }
166
+ /**
167
+ * Whether a candidate path is a real regular file. Symlinks and reparse
168
+ * points — the shape of broken Windows app-execution aliases such as a stale
169
+ * `WindowsApps\bash.exe` — are rejected, so a dead WSL alias never resolves as
170
+ * a Git Bash.
171
+ * @param candidate - the absolute candidate path.
172
+ * @returns true only for an existing non-link regular file.
173
+ */
123
174
  function candidateExists(candidate) {
124
175
  try {
125
176
  const stat = lstatSync(candidate);
126
- return stat.isFile() || stat.isSymbolicLink();
177
+ return stat.isFile();
127
178
  } catch {
128
179
  return false;
129
180
  }
@@ -379,4 +430,4 @@ var GitBashExecutor = class extends LocalBashExecutor {
379
430
  }
380
431
  };
381
432
  //#endregion
382
- export { GitBashExecutor, GitBashExecutor as default, candidateBashPaths, gitRootCandidates, gitToolPath, registryBashPaths, resolveBashPath };
433
+ export { GitBashExecutor, GitBashExecutor as default, candidateBashPaths, candidateExists, gitCandidatesUnder, gitRootCandidates, gitToolPath, probeDrives, registryBashPaths, resolveBashPath };
@@ -118,18 +118,15 @@ const DIALECT_FACTS = {
118
118
  msys: {
119
119
  shell: "Git Bash (MSYS2)",
120
120
  invoke: "bash -c",
121
- paths: "MSYS paths such as /d/WorkSpace or native C:\\...",
121
+ paths: "MSYS form (`/d/WorkSpace` or `C:\\...`)",
122
122
  env: "$VAR",
123
- toolchain: "the Git for Windows toolchain (git, Windows .exe tools, POSIX utilities)",
124
- conversion: "MSYS auto-converts leading-slash arguments to Windows paths whenever a native Windows executable is called, so POSIX paths get mangled (e.g. `wsl.exe -e ls /root` fails on `D:/Program Files/Git/root`); prefix such calls with `MSYS_NO_PATHCONV=1` to pass arguments verbatim"
123
+ note: "MSYS paths work inside Git Bash only — dsh's file tools (`read`, `write`, `edit`) on Windows take native `C:\\...` paths"
125
124
  },
126
125
  wsl: {
127
126
  shell: "WSL Linux",
128
127
  invoke: "bash -c",
129
- paths: "Linux paths such as /mnt/c/... (Windows paths auto-converted by wsl.exe)",
130
- env: "$VAR",
131
- toolchain: "the Linux userland (apt, gcc, python, ...)",
132
- conversion: "the command rides as a base64 payload, so quoting and Linux paths reach the distro verbatim (no MSYS-style mangling)"
128
+ paths: "Linux paths (`/mnt/c/...`)",
129
+ env: "$VAR"
133
130
  }
134
131
  };
135
132
  /** Runtime configuration schema for a shell tool instance. */
@@ -137,7 +134,10 @@ const Config$1 = z.object({ enableRunInBackground: z.boolean().default(true) });
137
134
  /**
138
135
  * The model-facing description of one shell tool instance. The POSIX variant
139
136
  * keeps the legacy wording byte-for-byte (the ACP/headless tool-schema
140
- * fixtures pin it); msys/wsl instances describe their dialect facts.
137
+ * fixtures pin it); msys/wsl instances mirror the official tool-pwsh
138
+ * skeleton (fresh shell, paths/env, exit codes, sandbox, truncation,
139
+ * background, escalation) with only their dialect's shell/paths/env facts
140
+ * plus at most one short dialect note.
141
141
  * @param dialect - the shell dialect the instance runs.
142
142
  * @param backgroundEnabled - whether `run_in_background` is advertised.
143
143
  * @param escalationModes - the escalation targets this composition advertises;
@@ -148,7 +148,8 @@ function shellDescription(dialect, backgroundEnabled, escalationModes) {
148
148
  const background = backgroundEnabled ? "Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`." : "Background execution is not available; long-running commands must finish within the timeout.";
149
149
  if (dialect === "posix") return `Execute a bash command (\`bash -c\`) and return its stdout/stderr. Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — pass \`workdir\` instead of using \`cd\`. Non-zero exits are reported as \`[exit code: N]\`. Current harness environment facts are exposed through managed \`\$${DSH_ENV_PREFIX}*\` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as \`[sandbox: file access denied under <mode> mode]\` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. ` + background + escalationTail(escalationModes);
150
150
  const facts = DIALECT_FACTS[dialect];
151
- return `Execute a ${facts.shell} command (${facts.invoke}) and return its stdout/stderr. Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — pass \`workdir\` instead of using \`cd\`. Paths use ${facts.paths}; read environment variables with ${facts.env}; ${facts.toolchain} is available. Note: ${facts.conversion}. Non-zero exits are reported as \`[exit code: N]\`. Current harness environment facts are exposed through managed \`\$${DSH_ENV_PREFIX}*\` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as \`[sandbox: file access denied under <mode> mode]\` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. ` + background + escalationTail(escalationModes);
151
+ const note = facts.note === void 0 ? "" : ` Note: ${facts.note}.`;
152
+ return `Execute a ${facts.shell} command (${facts.invoke}) and return its stdout/stderr. Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — pass \`workdir\` instead of using \`cd\`. Paths use ${facts.paths}; read environment variables with ${facts.env}.` + note + ` Non-zero exits are reported as \`[exit code: N]\`. Current harness environment facts are exposed through managed \`\$${DSH_ENV_PREFIX}*\` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as \`[sandbox: file access denied under <mode> mode]\` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. ` + background + escalationTail(escalationModes);
152
153
  }
153
154
  /**
154
155
  * The same-turn escalation guidance appended after a denial marker. Kept in
@@ -17,8 +17,19 @@ import { processOutcome } from "./background.js";
17
17
  import { parseExitStatus, renderProcessRead, renderResult } from "./render.js";
18
18
  const DIALECT_FACTS = {
19
19
  posix: { shell: 'bash', invoke: 'bash -c', paths: 'POSIX paths', env: '$VAR', toolchain: 'the full Unix toolchain' },
20
- msys: { shell: 'Git Bash (MSYS2)', invoke: 'bash -c', paths: 'MSYS paths such as /d/WorkSpace or native C:\\...', env: '$VAR', toolchain: 'the Git for Windows toolchain (git, Windows .exe tools, POSIX utilities)', conversion: 'MSYS auto-converts leading-slash arguments to Windows paths whenever a native Windows executable is called, so POSIX paths get mangled (e.g. `wsl.exe -e ls /root` fails on `D:/Program Files/Git/root`); prefix such calls with `MSYS_NO_PATHCONV=1` to pass arguments verbatim' },
21
- wsl: { shell: 'WSL Linux', invoke: 'bash -c', paths: 'Linux paths such as /mnt/c/... (Windows paths auto-converted by wsl.exe)', env: '$VAR', toolchain: 'the Linux userland (apt, gcc, python, ...)', conversion: 'the command rides as a base64 payload, so quoting and Linux paths reach the distro verbatim (no MSYS-style mangling)' },
20
+ msys: {
21
+ shell: 'Git Bash (MSYS2)',
22
+ invoke: 'bash -c',
23
+ paths: 'MSYS form (`/d/WorkSpace` or `C:\\...`)',
24
+ env: '$VAR',
25
+ note: 'MSYS paths work inside Git Bash only — dsh\'s file tools (`read`, `write`, `edit`) on Windows take native `C:\\...` paths',
26
+ },
27
+ wsl: {
28
+ shell: 'WSL Linux',
29
+ invoke: 'bash -c',
30
+ paths: 'Linux paths (`/mnt/c/...`)',
31
+ env: '$VAR',
32
+ },
22
33
  };
23
34
  /** Runtime configuration schema for a shell tool instance. */
24
35
  export const Config = z.object({
@@ -27,7 +38,10 @@ export const Config = z.object({
27
38
  /**
28
39
  * The model-facing description of one shell tool instance. The POSIX variant
29
40
  * keeps the legacy wording byte-for-byte (the ACP/headless tool-schema
30
- * fixtures pin it); msys/wsl instances describe their dialect facts.
41
+ * fixtures pin it); msys/wsl instances mirror the official tool-pwsh
42
+ * skeleton (fresh shell, paths/env, exit codes, sandbox, truncation,
43
+ * background, escalation) with only their dialect's shell/paths/env facts
44
+ * plus at most one short dialect note.
31
45
  * @param dialect - the shell dialect the instance runs.
32
46
  * @param backgroundEnabled - whether `run_in_background` is advertised.
33
47
  * @param escalationModes - the escalation targets this composition advertises;
@@ -49,11 +63,12 @@ export function shellDescription(dialect, backgroundEnabled, escalationModes) {
49
63
  return base + escalationTail(escalationModes);
50
64
  }
51
65
  const facts = DIALECT_FACTS[dialect];
66
+ const note = facts.note === undefined ? '' : ` Note: ${facts.note}.`;
52
67
  const base = `Execute a ${facts.shell} command (${facts.invoke}) and return its stdout/stderr. `
53
68
  + 'Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — '
54
69
  + 'pass `workdir` instead of using `cd`. '
55
- + `Paths use ${facts.paths}; read environment variables with ${facts.env}; ${facts.toolchain} is available. `
56
- + `Note: ${facts.conversion}. `
70
+ + `Paths use ${facts.paths}; read environment variables with ${facts.env}.`
71
+ + note + ' '
57
72
  + 'Non-zero exits are reported as `[exit code: N]`. '
58
73
  + `Current harness environment facts are exposed through managed \`\$${DSH_ENV_PREFIX}*\` variables; inspect them when needed. `
59
74
  + 'Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under <mode> mode]` — a policy denial, not a bug in the command; do not retry another way. '
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-win-multi-bash",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "author": "Dinosaur_MC",
5
5
  "repository": {
6
6
  "type": "git",