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.
- package/README.en.md +82 -197
- package/README.md +197 -194
- package/cordis.patch.yml +5 -5
- package/dist/client.core.js +46 -39
- package/dist/client.js +47 -40
- package/dsh.plugin.json +22 -22
- package/lib/client.js +148 -0
- package/lib/dsh-types.d.ts +1 -1
- package/lib/index.d.ts +17 -16
- package/lib/index.js +47 -47
- package/lib/terminal.d.ts +24 -1
- package/lib/terminal.js +214 -49
- package/package.json +38 -57
- package/screenshots.json +3 -0
- package/scripts/build-client.mjs +14 -13
- package/src/client.tsx +70 -46
- package/src/dsh-types.ts +1 -1
- package/src/index.ts +47 -47
- package/src/terminal.ts +254 -54
- package/README.ja.md +0 -196
- package/README.ko.md +0 -194
package/screenshots.json
ADDED
package/scripts/build-client.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
// Build the browser client bundle for dsh-bash-terminal
|
|
2
|
-
// Output:
|
|
3
|
-
//
|
|
4
|
-
//
|
|
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
|
|
24
|
-
//
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
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
|
|
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
|
|
2
|
-
// Web UI General settings
|
|
3
|
-
//
|
|
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
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
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
|
|
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
|
|
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
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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
|
-
<
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
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
|
|
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
|
|
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:
|
|
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
|
|
22
|
-
//
|
|
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
|
|
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
|
|
170
|
-
* Keeping it in the list only as a last-resort
|
|
171
|
-
* config docs reference, but a working
|
|
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
|
|
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
|
-
|
|
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_*
|
|
254
|
-
* appended
|
|
255
|
-
*
|
|
256
|
-
*
|
|
257
|
-
*
|
|
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
|
|
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
|
|
272
|
-
// via /etc/profile. Without it MSYS2 defaults to the bare MSYS
|
|
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
|
|
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((
|
|
289
|
-
.filter((
|
|
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
|
|
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
|
|
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
|
|
590
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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[];
|