dsh-bash-terminal-ts 0.2.6 → 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.
@@ -0,0 +1,3 @@
1
+ {
2
+ "screenshots": []
3
+ }
@@ -1,7 +1,8 @@
1
- // Build the browser client bundle for dsh-bash-terminal-ts.
2
- // Output: dist/client.js — a __ModuleLoader__.load({ id, factory }) wrapper
3
- // around the esbuild CJS bundle; shared deps (react, @deepseek-ai/*) resolve
4
- // through the loader's require.
1
+ // Build the browser client bundle for dsh-bash-terminal.
2
+ // Output: lib/client.js (primary, consumed by DSH loader/reload precheck) and
3
+ // dist/client.js (legacy compatibility) — a __ModuleLoader__.load({ id, factory })
4
+ // wrapper around the esbuild CJS bundle; shared deps (react, @deepseek-ai/*)
5
+ // resolve through the loader's require.
5
6
 
6
7
  import { build } from "esbuild";
7
8
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
@@ -10,6 +11,7 @@ import { dirname, join } from "node:path";
10
11
 
11
12
  const root = dirname(dirname(fileURLToPath(import.meta.url)));
12
13
  mkdirSync(join(root, "dist"), { recursive: true });
14
+ mkdirSync(join(root, "lib"), { recursive: true });
13
15
 
14
16
  await build({
15
17
  entryPoints: [join(root, "src", "client.tsx")],
@@ -20,20 +22,18 @@ await build({
20
22
  external: ["react", "react/jsx-runtime", "react-dom", "@deepseek-ai/*"],
21
23
  jsx: "automatic",
22
24
  // Pin the TS options instead of letting esbuild discover a tsconfig by walking
23
- // up from the entry point. The repo root has a tsconfig.json, so in-repo builds
24
- // silently inherited it and emitted a leading "use strict"; while a byte-for-byte
25
- // identical build from a worktree outside the repo (no tsconfig above it) did not
26
- // — same source, two different committed artifacts. These values are what the
27
- // root tsconfig actually supplied. The one observable delta is that stray
28
- // "use strict"; (inert: the core is wrapped in a function expression by the
29
- // loader shim below, so it was never a directive prologue), now gone everywhere.
25
+ // up from the entry point: an in-repo build found the repo-root tsconfig.json
26
+ // (emitting a leading "use strict";) while the same source built from an
27
+ // out-of-repo worktree did not — two different committed artifacts for one
28
+ // source. These are the values that tsconfig actually supplied, so behavior is
29
+ // unchanged; only the stray line disappears.
30
30
  tsconfigRaw: { compilerOptions: { target: "ES2022", useDefineForClassFields: true } },
31
31
  logLevel: "warning"
32
32
  });
33
33
 
34
34
  const core = readFileSync(join(root, "dist", "client.core.js"), "utf8");
35
35
  const wrapper = `window.__ModuleLoader__.load({
36
- id: "dsh-bash-terminal-ts",
36
+ id: "dsh-bash-terminal",
37
37
  factory: (require) => {
38
38
  var module = { exports: {} };
39
39
  var exports = module.exports;
@@ -42,5 +42,6 @@ ${core}
42
42
  }
43
43
  });
44
44
  `;
45
+ writeFileSync(join(root, "lib", "client.js"), wrapper);
45
46
  writeFileSync(join(root, "dist", "client.js"), wrapper);
46
- console.log("built dist/client.js (" + wrapper.length + " bytes)");
47
+ console.log("built lib/client.js + dist/client.js (" + wrapper.length + " bytes)");
package/src/client.tsx CHANGED
@@ -1,20 +1,46 @@
1
- // dsh-bash-terminal-ts client plugin: a "Default terminal" preference row in the
2
- // Web UI General settings. The user picks powershell / gitbash / msys2 / wsl;
3
- // the host shell tool obeys that choice (the model cannot change it).
1
+ // dsh-bash-terminal client plugin: a "Default terminal" preference row in the
2
+ // Web UI General settings, mirroring the shipped EnterBehaviorRow grammar
3
+ // (row layout, capsule selector with chevron, --dsw-* tokens).
4
4
  //
5
- // The option list below must stay in lockstep with SHELLS in ./index.ts: every
6
- // id gets an <option> here and a `shell.<id>` entry in both dictionaries. The
7
- // shipped bundle cannot import the host module (it pulls node: builtins), so
8
- // test/client.ts asserts the bundle covers every host shell id instead.
5
+ // The row offers one entry per host shell id — powershell / gitbash / msys2 /
6
+ // wsl — in the same order as SHELLS in ./index.ts.
7
+ //
8
+ // DSH >= 0.1.5: the browser module table (PLATFORM_MODULES) seeds react,
9
+ // @deepseek-ai/cordis, @deepseek-ai/dsh-client-store, @deepseek-ai/dsh-client-ui-slots,
10
+ // @deepseek-ai/dsh-client-ui-primitives and @deepseek-ai/dsh-client-ui-dockkit by
11
+ // exact bare specifier. The old dsh-client-runtime name is gone from that table
12
+ // (and so is its /client subpath), so this bundle requests dsh-client-store.
9
13
 
14
+ import { useState } from "react";
10
15
  import { defineStore } from "@deepseek-ai/dsh-client-store";
16
+ import { IconChevronDownOutline14, Menu } from "@deepseek-ai/dsh-client-ui-primitives";
11
17
 
12
18
  const SETTINGS_NS = "settings.bash-terminal";
13
19
  const SETTINGS_NAMESPACE = "bash-terminal";
20
+ const SHELLS = ["powershell", "gitbash", "msys2", "wsl"] as const;
21
+
22
+ type ShellOption = (typeof SHELLS)[number];
23
+
24
+ // Injected once when the browser loads the bundle (node tests guard on document).
25
+ const ROW_CSS =
26
+ ".btRow{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:center;gap:8px;padding:16px 0;display:flex}" +
27
+ ".btRowText{flex-direction:column;flex:1;gap:4px;min-width:0;padding-right:48px;display:flex}" +
28
+ ".btTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}" +
29
+ ".btDesc{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}" +
30
+ ".btSelector{background:var(--dsw-alias-bg-module-platform);height:36px;font:inherit;color:var(--dsw-alias-label-primary);cursor:pointer;border:none;border-radius:18px;align-items:center;gap:12px;padding:0 14px;font-size:14px;line-height:22px;display:inline-flex}" +
31
+ ".btSelector:hover{background:var(--dsw-alias-interactive-bg-hover)}" +
32
+ ".btChevron{flex:none}";
33
+ if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=\"bash-terminal-row\"]") === null) {
34
+ const tag = document.createElement("style");
35
+ tag.dataset.plugin = "dsh-bash-terminal";
36
+ tag.dataset.pluginCss = "bash-terminal-row";
37
+ tag.textContent = ROW_CSS;
38
+ document.head.appendChild(tag);
39
+ }
14
40
 
15
41
  const zh: Record<string, string> = {
16
42
  "shell.title": "默认终端",
17
- "shell.description": "shell 工具执行命令时使用的终端(由你决定,AI 无法更改)",
43
+ "shell.description": "shell 工具执行命令时使用的终端",
18
44
  "shell.powershell": "PowerShell",
19
45
  "shell.gitbash": "Git Bash",
20
46
  "shell.msys2": "MSYS2",
@@ -22,7 +48,7 @@ const zh: Record<string, string> = {
22
48
  };
23
49
  const en: Record<string, string> = {
24
50
  "shell.title": "Default terminal",
25
- "shell.description": "Terminal used by the shell tool (you control this; the AI cannot change it)",
51
+ "shell.description": "Terminal used by the shell tool",
26
52
  "shell.powershell": "PowerShell",
27
53
  "shell.gitbash": "Git Bash",
28
54
  "shell.msys2": "MSYS2",
@@ -47,43 +73,40 @@ interface ShellPreferenceRowProps {
47
73
  function ShellPreferenceRow({ t, useStore, setShell }: ShellPreferenceRowProps) {
48
74
  const shell = useStore((s) => s.shell);
49
75
  const writable = useStore((s) => s.writable);
76
+ const [open, setOpen] = useState(false);
77
+ const items = SHELLS.map((id) => ({ id, label: t("shell." + id) }));
50
78
  return (
51
- <div
52
- style={{
53
- display: "flex",
54
- alignItems: "center",
55
- justifyContent: "space-between",
56
- gap: 16,
57
- padding: "12px 0"
58
- }}
59
- >
60
- <div style={{ minWidth: 0 }}>
61
- <div style={{ fontSize: 14, fontWeight: 500, lineHeight: "22px", color: "var(--dsw-alias-label-primary, inherit)" }}>
62
- {t("shell.title")}
63
- </div>
64
- <div style={{ fontSize: 12, lineHeight: "18px", opacity: 0.65 }}>{t("shell.description")}</div>
79
+ <div className="btRow">
80
+ <div className="btRowText">
81
+ <div className="btTitle">{t("shell.title")}</div>
82
+ <div className="btDesc">{t("shell.description")}</div>
65
83
  </div>
66
- <select
67
- value={shell}
68
- disabled={!writable}
69
- onChange={(e) => setShell(e.target.value)}
70
- style={{
71
- fontSize: 14,
72
- padding: "6px 10px",
73
- borderRadius: 8,
74
- border: "1px solid var(--dsw-alias-line-strong, #ccc)",
75
- background: "var(--dsw-alias-bg-layer-2, #fff)",
76
- color: "var(--dsw-alias-label-primary, inherit)",
77
- outline: "none",
78
- cursor: writable ? "pointer" : "not-allowed",
79
- maxWidth: 180
84
+ <Menu
85
+ open={open}
86
+ onClose={() => setOpen(false)}
87
+ items={items}
88
+ selectedId={shell}
89
+ onSelect={(id) => {
90
+ setOpen(false);
91
+ setShell(id);
80
92
  }}
81
- >
82
- <option value="powershell">{t("shell.powershell")}</option>
83
- <option value="gitbash">{t("shell.gitbash")}</option>
84
- <option value="msys2">{t("shell.msys2")}</option>
85
- <option value="wsl">{t("shell.wsl")}</option>
86
- </select>
93
+ align="end"
94
+ portal
95
+ anchor={
96
+ <button
97
+ type="button"
98
+ className="btSelector"
99
+ aria-haspopup="menu"
100
+ aria-expanded={open}
101
+ disabled={!writable}
102
+ onClick={() => setOpen(!open)}
103
+ style={!writable ? { opacity: 0.5, cursor: "not-allowed" } : undefined}
104
+ >
105
+ {t("shell." + shell)}
106
+ <IconChevronDownOutline14 className="btChevron" />
107
+ </button>
108
+ }
109
+ />
87
110
  </div>
88
111
  );
89
112
  }
@@ -116,6 +139,10 @@ interface RowStoreActions {
116
139
  sync(shell?: string, revision?: number, writable?: boolean): void;
117
140
  }
118
141
 
142
+ interface RowStore {
143
+ actions: RowStoreActions;
144
+ }
145
+
119
146
  export function apply(ctx: ClientContext): void {
120
147
  ctx.effect(() => ctx.locale.register(SETTINGS_NS, { zh, en }), "bash-terminal: settings dictionaries");
121
148
  const scope = ctx.settingsScope.bind({ namespace: SETTINGS_NAMESPACE });
@@ -130,9 +157,6 @@ export function apply(ctx: ClientContext): void {
130
157
  }
131
158
  }
132
159
  });
133
- // defineStore() yields a { spec, create } factory; slots.register creates
134
- // the live store, so we bind the actions in the inject callback and push
135
- // the initial snapshot there too (mirrors the theme row pattern).
136
160
  let bound: RowStoreActions | undefined;
137
161
  const push = (snap: SettingsSnapshot): void => {
138
162
  bound?.sync(snap.value?.defaultShell, snap.revision, snap.writable);
package/src/dsh-types.ts CHANGED
@@ -294,7 +294,7 @@ export interface ApprovalSeam {
294
294
  // ---- plugin context (server side) ----------------------------------------------
295
295
 
296
296
  /**
297
- * The structural context face dsh-bash-terminal-ts programs against: the union
297
+ * The structural context face dsh-bash-terminal programs against: the union
298
298
  * of every `ctx.*` member touched by src/index.ts and src/terminal.ts.
299
299
  */
300
300
  export interface BashTerminalContext {
package/src/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- // dsh-bash-terminal-ts - one shell tool, four Windows terminals.
1
+ // dsh-bash-terminal - one shell tool, four Windows terminals.
2
2
  //
3
3
  // Registers a model-facing shell tool. The terminal backend (powershell /
4
4
  // gitbash / msys2 / wsl) is chosen by the USER in the Web UI settings (default
@@ -6,11 +6,7 @@
6
6
  // choice:
7
7
  // - powershell: pwsh -NoLogo -NoProfile -NonInteractive -Command <cmd>
8
8
  // - gitbash: Git for Windows bash -lc <cmd> (POSIX; /d/... paths)
9
- // - msys2: C:\msys64\usr\bin\bash.exe -lc <cmd> (POSIX; full GCC/mingw64
10
- // toolchain). NOT msys2.exe: that Cygwin launcher allocates a
11
- // console and returns exit 0 with zero bytes under piped stdio.
12
- // MSYSTEM=MINGW64 is injected via buildEnv so /etc/profile picks
13
- // the MINGW64 environment and /mingw64/bin lands on PATH.
9
+ // - msys2: MSYS2 bash -lc <cmd> (POSIX; /c/... paths, full GCC/mingw64 toolchain)
14
10
  // - wsl: wsl [-d <distro>] -e bash -lc <cmd> (Linux; /mnt/d/... paths)
15
11
  //
16
12
  // The tool spawns through the shared ctx.subprocess seam (process-tree
@@ -18,8 +14,13 @@
18
14
  // handles with the generic ctx.jobs registry, mirroring the shipped
19
15
  // dsh-tool-bash / dsh-tool-pwsh story call-for-call. It deliberately
20
16
  // does NOT consume the ctx.shell capability seam: the platform's own
21
- // sandboxed PowerShell executor keeps serving the pwsh tool, and this tool
22
- // is an additional, user-selected terminal that runs outside the sandbox.
17
+ // sandboxed PowerShell executor keeps serving the pwsh tool. This tool is
18
+ // an additional, user-selected terminal that still honors the DSH sandbox:
19
+ // confined modes wrap PowerShell's spawn argv through ctx.sandbox
20
+ // (fail-closed), while danger-full-access, Git Bash, MSYS2, and WSL run
21
+ // unconfined. Git Bash and MSYS2 are unconfined because the DSH Windows ACL
22
+ // restricted-token runner cannot host Cygwin/MSYS2: bash aborts with
23
+ // "CreateFileMapping ... Win32 error 5" during startup.
23
24
 
24
25
  import { lstatSync } from "node:fs";
25
26
  import { isAbsolute, join, resolve } from "node:path";
@@ -161,33 +162,29 @@ export function candidateGitBashPaths(env: NodeJS.ProcessEnv = process.env): str
161
162
  }
162
163
 
163
164
  /**
164
- * MSYS2 locations, in preference order: the real `bash.exe` under usr\bin first,
165
- * then bin\bash.exe, and `msys2.exe` dead last.
165
+ * MSYS2 locations, in preference order: the real `bash.exe` under usr\bin
166
+ * first, then bin\bash.exe, and `msys2.exe` dead last.
166
167
  *
167
168
  * msys2.exe is NOT a usable backend for piped execution: it is the console-
168
169
  * allocating Cygwin launcher, so a spawn with piped stdio returns exit 0 with
169
- * zero bytes on both stdout and stderr (measured on MSYS2 with bash 5.3.15).
170
- * Keeping it in the list only as a last-resort fallback preserves the path the
171
- * config docs reference, but a working bash.exe always wins.
170
+ * zero bytes on both stdout and stderr (measured on this machine against
171
+ * MSYS2 with bash 5.3.15). Keeping it in the list only as a last-resort
172
+ * fallback preserves the path the config docs reference, but a working
173
+ * bash.exe always wins.
172
174
  *
173
- * MSYS2 uses the same Cygwin/MSYS2 runtime as Git Bash, so it cannot run under
174
- * the DSH Windows ACL restricted-token sandbox.
175
+ * MSYS2 uses the same Cygwin/MSYS2 runtime as Git Bash, so it cannot run
176
+ * under the DSH Windows ACL restricted-token sandbox.
175
177
  */
176
178
  export function candidateMsys2Paths(env: NodeJS.ProcessEnv = process.env): string[] {
177
- const programFiles = env.ProgramFiles ?? "C:\\Program Files";
178
179
  const candidates = [
179
180
  "C:\\msys64\\usr\\bin\\bash.exe",
180
181
  "C:\\msys64\\bin\\bash.exe"
181
182
  ];
182
- // Also check 32-bit variant
183
- const programFilesX86 = env["ProgramFiles(x86)"] ?? "C:\\Program Files (x86)";
184
- if (programFilesX86 !== programFiles) {
185
- candidates.push("C:\\msys64\\usr\\bin\\bash.exe"); // same path regardless of arch
186
- }
187
183
  for (const entry of (env.PATH ?? "").split(";")) {
188
184
  const trimmed = entry.trim().replace(/^"|"$/g, "");
189
185
  if (trimmed.length === 0) continue;
190
- if (trimmed.toLowerCase().includes("msys64") || trimmed.toLowerCase().includes("mingw64")) {
186
+ const lower = trimmed.toLowerCase();
187
+ if (lower.includes("msys64") || lower.includes("mingw64")) {
191
188
  candidates.push(join(trimmed, "bash.exe"));
192
189
  }
193
190
  }
@@ -250,16 +247,16 @@ export function buildArgv(
250
247
 
251
248
  /**
252
249
  * Merge the DSH_* environment over the process environment. For WSL, only
253
- * variables explicitly listed in WSLENV cross the boundary, so every DSH_* key is
254
- * appended to it (WSLENV is a `:` separated VAR[/flag] list).
255
- *
256
- * WSLENV usually already exists for reasons unrelated to us -- Windows Terminal
257
- * exports e.g. `WT_SESSION:WT_PROFILE_ID:` -- so the list is layered rather than
258
- * rebuilt, or those entries would be silently dropped from every WSL call.
259
- * Callers that spawn through a seam which replaces the parent environment
260
- * wholesale (the PTY path) must pass the inherited value explicitly.
250
+ * variables explicitly listed in WSLENV cross the boundary, so every DSH_*
251
+ * key is appended there — and the list is *layered onto* whatever WSLENV the
252
+ * host already had (Windows Terminal exports e.g. `WT_SESSION:WT_PROFILE_ID:`),
253
+ * never rebuilt from scratch: WSLENV is an allow-list, so dropping the
254
+ * inherited entries would silently stop them crossing into WSL.
261
255
  *
262
- * @param inheritedWslenv - the WSLENV the child would otherwise inherit.
256
+ * @param inheritedWslenv - the ambient WSLENV to layer onto. Callers that
257
+ * replace the child environment wholesale (the PTY path) must pass
258
+ * `process.env.WSLENV` explicitly, because the ambient value is not visible
259
+ * through `dshEnv`.
263
260
  */
264
261
  export function buildEnv(
265
262
  shell: string,
@@ -268,25 +265,27 @@ export function buildEnv(
268
265
  ): Record<string, string | undefined> {
269
266
  const env: Record<string, string | undefined> = { ...ENV_OVERRIDES, ...dshEnv };
270
267
  if (shell === "msys2") {
271
- // Picks the MINGW64 environment, so /mingw64/bin (gcc, make, ...) joins PATH
272
- // via /etc/profile. Without it MSYS2 defaults to the bare MSYS environment.
268
+ // Picks the MINGW64 environment, so /mingw64/bin (gcc, make, ...) joins
269
+ // PATH via /etc/profile. Without it MSYS2 defaults to the bare MSYS
270
+ // environment. An explicit caller-supplied value wins.
273
271
  if (env.MSYSTEM === undefined) env.MSYSTEM = "MINGW64";
274
272
  }
275
273
  if (shell === "wsl") {
276
274
  const keys = Object.keys(dshEnv ?? {});
277
275
  if (keys.length > 0) {
278
- // An explicit WSLENV from the caller wins over what we would inherit.
276
+ // An explicit WSLENV in dshEnv wins over the inherited one; otherwise the
277
+ // inherited value is the base. Either way the base is SPLIT into entries
278
+ // (the host value ends with a trailing ":", so string concatenation would
279
+ // produce a malformed empty entry) and the WSLENV key itself is excluded
280
+ // (it is a key of dshEnv too, and appending it would add a bogus entry).
279
281
  const declared = Object.prototype.hasOwnProperty.call(dshEnv ?? {}, "WSLENV");
280
282
  const base = declared
281
283
  ? env.WSLENV
282
284
  : (typeof inheritedWslenv === "string" && inheritedWslenv.length > 0 ? inheritedWslenv : env.WSLENV);
283
- // Windows Terminal's own value ends with a trailing ':' and the caller's
284
- // may too, so split/normalise instead of string-concatenating -- a naive
285
- // join yields an empty entry ("A:B::DSH_X"), which WSL flags as malformed.
286
285
  const parts = typeof base === "string" ? base.split(":") : [];
287
286
  env.WSLENV = [...parts, ...keys.filter((k) => k !== "WSLENV").flatMap((k) => k.split(":"))]
288
- .map((part) => part.trim())
289
- .filter((part) => part.length > 0)
287
+ .map((p) => p.trim())
288
+ .filter((p) => p.length > 0)
290
289
  .join(":");
291
290
  }
292
291
  }
@@ -322,7 +321,7 @@ function spawnSpec(resolved: SpawnResolution, argv: string[], env: Record<string
322
321
  function collectedOutput(handle: { collected: { stdout: unknown; stderr: unknown } }) {
323
322
  const { stdout, stderr } = handle.collected;
324
323
  if (!isCollectedStream(stdout) || !isCollectedStream(stderr)) {
325
- throw new Error("dsh-bash-terminal-ts: subprocess implementation dropped a requested collect stream");
324
+ throw new Error("dsh-bash-terminal: subprocess implementation dropped a requested collect stream");
326
325
  }
327
326
  return { stdout, stderr };
328
327
  }
@@ -393,7 +392,7 @@ async function runForeground(
393
392
  function requireSubprocess(ctx: BashTerminalContext) {
394
393
  const subprocess = ctx.subprocess;
395
394
  if (subprocess === null) {
396
- throw new Error("dsh-bash-terminal-ts: ctx.subprocess seam unavailable (missing inject service)");
395
+ throw new Error("dsh-bash-terminal: ctx.subprocess seam unavailable (missing inject service)");
397
396
  }
398
397
  return subprocess;
399
398
  }
@@ -586,8 +585,9 @@ export function validateArgs(args: Record<string, unknown>): ValidatedShellArgs
586
585
  * Git Bash and MSYS2 are also not confined: DSH's Windows ACL restricted-token
587
586
  * runner cannot start Cygwin/MSYS2 (CreateFileMapping Win32 error 5), so
588
587
  * attempting to wrap them would abort every Git Bash / MSYS2 command. A
589
- * requested confined mode with no usable backend throws the fail-closed
590
- * SandboxUnavailableError, exactly like the shipped executors.
588
+ * requested confined mode
589
+ * with no usable backend throws the fail-closed SandboxUnavailableError,
590
+ * exactly like the shipped executors.
591
591
  */
592
592
  function confineSpawn(
593
593
  ctx: BashTerminalContext,
@@ -666,14 +666,14 @@ const BACKGROUND_OUTPUT_PROPERTIES: Record<string, JsonSchemaNode> = {
666
666
 
667
667
  export function apply(ctx: BashTerminalContext, config: Partial<ConfigValues> = {}): void {
668
668
  if (process.platform !== "win32") {
669
- ctx.logger?.info?.("dsh-bash-terminal-ts: only meaningful on win32; skipping tool registration");
669
+ ctx.logger?.info?.("dsh-bash-terminal: only meaningful on win32; skipping tool registration");
670
670
  return;
671
671
  }
672
672
  const backgroundEnabled = true;
673
673
  const paths = resolveAllPaths(config);
674
674
  const defaultShell = config.defaultShell ?? DEFAULT_SHELL;
675
675
  if (!(SHELLS as readonly string[]).includes(defaultShell)) {
676
- throw new Error(`dsh-bash-terminal-ts: invalid defaultShell ${JSON.stringify(defaultShell)}`);
676
+ throw new Error(`dsh-bash-terminal: invalid defaultShell ${JSON.stringify(defaultShell)}`);
677
677
  }
678
678
  const settingsScope = ctx.settings.register(
679
679
  SETTINGS_NAMESPACE,
@@ -706,7 +706,7 @@ export function apply(ctx: BashTerminalContext, config: Partial<ConfigValues> =
706
706
  ctx.systemPrompt.section({
707
707
  name: "tool:bash-terminal",
708
708
  order: 105,
709
- text: "Use the shell tool for terminal commands: it runs in the terminal the user chose in Settings -> General -> Default terminal (PowerShell, Git Bash, or WSL) and honors the DSH sandbox. Prefer it over the pwsh tool for everyday commands; keep the pwsh tool for cases that specifically need the sandboxed PowerShell surface."
709
+ text: "Use the shell tool for terminal commands: it runs in the terminal the user chose in Settings -> General -> Default terminal (PowerShell, Git Bash, MSYS2, or WSL) and honors the DSH sandbox. Prefer it over the pwsh tool for everyday commands; keep the pwsh tool for cases that specifically need the sandboxed PowerShell surface."
710
710
  });
711
711
 
712
712
  const toolName = "shell";
@@ -829,7 +829,7 @@ export function apply(ctx: BashTerminalContext, config: Partial<ConfigValues> =
829
829
  const shell = settingsScope.get().defaultShell;
830
830
  const argv0 = buildArgv(shell, v.command, paths, v.distro);
831
831
  if (argv0[0] === undefined) {
832
- throw new Error(`dsh-bash-terminal-ts: ${shell} backend unavailable - executable not found. Install it or set the corresponding *Path config.`);
832
+ throw new Error(`dsh-bash-terminal: ${shell} backend unavailable - executable not found. Install it or set the corresponding *Path config.`);
833
833
  }
834
834
  // Only element 0 (the resolved executable) can be undefined; guarded above.
835
835
  const argv = argv0 as string[];