dsh-bash-terminal-ts 0.7.1 → 0.8.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 +99 -1
- package/README.md +195 -2
- package/cordis.patch.yml +66 -0
- package/dist/client.core.js +8018 -157
- package/dist/client.js +8018 -157
- package/dsh.plugin.json +22 -22
- package/lib/browser-i18n.d.ts +4 -0
- package/lib/browser-i18n.js +23 -0
- package/lib/client.js +8018 -157
- package/lib/dsh-types.d.ts +10 -0
- package/lib/dsh.d.ts +2 -0
- package/lib/index.d.ts +43 -0
- package/lib/index.js +196 -10
- package/lib/locales.d.ts +4 -0
- package/lib/locales.js +422 -0
- package/lib/settings-surface.d.ts +20 -0
- package/lib/settings-surface.js +49 -0
- package/lib/shell-id.d.ts +2 -0
- package/lib/shell-id.js +9 -0
- package/lib/shells.d.ts +114 -0
- package/lib/shells.js +187 -0
- package/lib/terminal.d.ts +30 -1
- package/lib/terminal.js +18 -5
- package/lib/webterminals/catalog.d.ts +36 -0
- package/lib/webterminals/catalog.js +93 -0
- package/lib/webterminals/host.d.ts +67 -0
- package/lib/webterminals/host.js +278 -0
- package/lib/webterminals/mount.d.ts +46 -0
- package/lib/webterminals/mount.js +171 -0
- package/lib/webterminals/protocol.d.ts +94 -0
- package/lib/webterminals/protocol.js +9 -0
- package/lib/webterminals/pty.d.ts +42 -0
- package/lib/webterminals/pty.js +64 -0
- package/lib/webterminals/registry.d.ts +139 -0
- package/lib/webterminals/registry.js +353 -0
- package/lib/webterminals/ws.d.ts +54 -0
- package/lib/webterminals/ws.js +140 -0
- package/locale/de.json +4 -0
- package/locale/en.json +4 -0
- package/locale/es.json +4 -0
- package/locale/fr.json +4 -0
- package/locale/it.json +4 -0
- package/locale/ja.json +4 -0
- package/locale/ko.json +4 -0
- package/locale/ru.json +4 -0
- package/locale/zh.json +4 -0
- package/package.json +131 -125
- package/scripts/build-browser-tests.mjs +36 -0
- package/scripts/build-client.mjs +10 -0
- package/scripts/check-artifacts.mjs +93 -0
- package/scripts/link-shells.mjs +75 -0
- package/src/browser-i18n.ts +27 -0
- package/src/client.tsx +622 -192
- package/src/dsh-types.ts +6 -0
- package/src/dsh.ts +2 -0
- package/src/index.ts +226 -10
- package/src/locales.ts +434 -0
- package/src/settings-surface.ts +59 -0
- package/src/shell-id.ts +11 -0
- package/src/shells.ts +250 -0
- package/src/terminal.ts +34 -5
- package/src/webterminals/browser/api.ts +212 -0
- package/src/webterminals/browser/bridge.ts +188 -0
- package/src/webterminals/browser/css.d.ts +16 -0
- package/src/webterminals/browser/face.ts +151 -0
- package/src/webterminals/browser/shell-picker.tsx +98 -0
- package/src/webterminals/browser/store.ts +40 -0
- package/src/webterminals/browser/tab.tsx +136 -0
- package/src/webterminals/browser/testkit.ts +21 -0
- package/src/webterminals/browser/url.ts +70 -0
- package/src/webterminals/browser/view.ts +381 -0
- package/src/webterminals/browser/xterm.ts +269 -0
- package/src/webterminals/catalog.ts +114 -0
- package/src/webterminals/host.ts +361 -0
- package/src/webterminals/mount.ts +227 -0
- package/src/webterminals/protocol.ts +78 -0
- package/src/webterminals/pty.ts +90 -0
- package/src/webterminals/registry.ts +447 -0
- package/src/webterminals/ws.ts +166 -0
- package/tsconfig.client.json +9 -1
- package/tsconfig.json +1 -1
package/README.en.md
CHANGED
|
@@ -70,6 +70,66 @@ pnpm rebuild node-pty
|
|
|
70
70
|
|
|
71
71
|
For local development (junction install, source changes apply instantly) see the Chinese README's development section.
|
|
72
72
|
|
|
73
|
+
## Shell aliases: get Git Bash / MSYS2 into the host's own terminal menu (optional)
|
|
74
|
+
|
|
75
|
+
This plugin's own “Terminal (multi-shell)” tab needs nothing extra — all four shells work out of the box. The **host's** official right-sidebar terminal is a different story: its menu is built by `discoverShells`, which keys a Map by the executable **file name** and keeps the earliest entry, and both Git Bash and MSYS2 ship as `bash.exe`. At most one of them can ever appear there.
|
|
76
|
+
|
|
77
|
+
The bundled `cordis.patch.yml` therefore adds two candidates that carry a file name of their own: `C:\msys64\usr\bin\msys-bash.exe` and `C:\Program Files\Git\bin\git-bash.exe`. Both are hard links (same inode, no extra space, delete them to revert), and they only produce a menu entry if they actually exist on the machine. A missing link is skipped per candidate, so the menu stays exactly as it was and nothing fails.
|
|
78
|
+
|
|
79
|
+
Create the aliases (either route):
|
|
80
|
+
|
|
81
|
+
```powershell
|
|
82
|
+
# A. the script shipped with this package: creates what it can, prints the elevated
|
|
83
|
+
# command verbatim for what it cannot
|
|
84
|
+
node scripts/link-shells.mjs
|
|
85
|
+
|
|
86
|
+
# B. by hand (the MSYS2 line works as a standard user; the Git line needs an ELEVATED PowerShell / cmd)
|
|
87
|
+
cmd /c mklink /H "C:\msys64\usr\bin\msys-bash.exe" "C:\msys64\usr\bin\bash.exe"
|
|
88
|
+
cmd /c mklink /H "C:\Program Files\Git\bin\git-bash.exe" "C:\Program Files\Git\bin\bash.exe"
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
With no administrator terminal at hand, pull one up from an ordinary session (Windows asks once; click **Yes**):
|
|
92
|
+
|
|
93
|
+
```powershell
|
|
94
|
+
Start-Process cmd -Verb RunAs -Wait -ArgumentList '/c','mklink /H "C:\Program Files\Git\bin\git-bash.exe" "C:\Program Files\Git\bin\bash.exe" > "%TEMP%\mklink.log" 2>&1'
|
|
95
|
+
Get-Content "$env:TEMP\mklink.log"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
The alias behaves exactly like the original (measured on `git-bash.exe -i`: `MSYSTEM=MINGW64`, POSIX root `C:\Program Files\Git\`, `$0=/usr/bin/bash`, `git` on PATH) - because the link stays inside `Git\bin\`, so bash still derives the same root from its own path.
|
|
99
|
+
|
|
100
|
+
The Git line cannot avoid elevation, for two independent reasons: `CreateHardLink` requires write access to the **source** file, which Program Files denies a standard user (so the link cannot be created from somewhere else either); and the link has to stay inside `Git\bin\`, because bash derives its POSIX root from its own path and loses `/usr/bin` and `/etc` anywhere else.
|
|
101
|
+
|
|
102
|
+
> **Hand it to an agent** — paste this to an agent running with full (administrator) permissions:
|
|
103
|
+
>
|
|
104
|
+
> ```text
|
|
105
|
+
> Create two shell alias hard links for the DSH host's official terminal, with administrator rights:
|
|
106
|
+
> cmd /c mklink /H "C:\msys64\usr\bin\msys-bash.exe" "C:\msys64\usr\bin\bash.exe"
|
|
107
|
+
> cmd /c mklink /H "C:\Program Files\Git\bin\git-bash.exe" "C:\Program Files\Git\bin\bash.exe"
|
|
108
|
+
> Skip creation if the target already exists; never overwrite it. Then verify with this package's
|
|
109
|
+
> `node scripts/link-shells.mjs` - both lines must report `ok ... (same inode, nlink=2)`. Finally
|
|
110
|
+
> restart DSH so the official terminal re-discovers its shells.
|
|
111
|
+
> Constraints: do not copy the files, do not create symbolic links, do not put the links in any
|
|
112
|
+
> other directory, do not modify bash.exe itself. If you are not already in an administrator
|
|
113
|
+
> context, start one with `Start-Process cmd -Verb RunAs` (Windows prompts once) and redirect the
|
|
114
|
+
> output to a file you can read back. If elevation fails, report the exact command and error code
|
|
115
|
+
> back to me instead of falling back to a copy.
|
|
116
|
+
> ```
|
|
117
|
+
|
|
118
|
+
After restarting DSH, the official “New” dropdown should list `msys-bash.exe` and `git-bash.exe`, while plain `bash` (on Windows that resolves to the WSL forwarder `C:\Windows\System32\bash.exe`) **must still be there** — this package never claims the `bash` key for Git and pushes WSL out of the menu.
|
|
119
|
+
|
|
120
|
+
> ⚠️ **MSYS2 needs one more step: a PATH guard in `~/.bashrc`** (`C:\msys64\home\<you>\.bashrc`). The host starts every candidate as an interactive **non-login** shell (`shellCandidates` is a plain string array, so `-l` cannot be passed), and a non-login shell never sources `/etc/profile` — which is exactly where MSYS2 adds `/usr/bin` to PATH. Without this, `msys-bash.exe` opens fine but `uname`/`ls` report “command not found”. Git Bash does **not** need it: it injects `/usr/bin` and `/mingw64/bin` itself (verified: `cygpath` and `git` work immediately).
|
|
121
|
+
>
|
|
122
|
+
> ```bash
|
|
123
|
+
> # >>> dsh-bash-terminal-ts PATH guard >>>
|
|
124
|
+
> case ":$PATH:" in
|
|
125
|
+
> *":/usr/bin:"*) ;;
|
|
126
|
+
> *) PATH="/usr/bin:$PATH" ;;
|
|
127
|
+
> esac
|
|
128
|
+
> # <<< dsh-bash-terminal-ts PATH guard <<<
|
|
129
|
+
> ```
|
|
130
|
+
>
|
|
131
|
+
> Prepending is safe here because `/usr/bin/gcc.exe` does not exist, so the mingw64 toolchain still resolves first (verified: `command -v gcc` stays `/mingw64/bin/gcc`). **Re-measure after any `pacman -S mingw-w64-*` install**, or the guard can start shadowing your compiler instead. To revert: delete everything between the two sentinel comments.
|
|
132
|
+
|
|
73
133
|
## Sandbox
|
|
74
134
|
|
|
75
135
|
- `danger-full-access` sessions run directly (no wrapping).
|
|
@@ -91,9 +151,47 @@ For local development (junction install, source changes apply instantly) see the
|
|
|
91
151
|
- **node-pty accepts no named signals on Windows**: `signal` maps `SIGINT` to Ctrl+C (`\x03`); other signals (`SIGTERM` / `SIGKILL` / `SIGTSTP` / `SIGHUP`) degrade to terminating the session.
|
|
92
152
|
- Git Bash interactive sessions work fully.
|
|
93
153
|
|
|
154
|
+
## Bottom terminal (embedded browser shell, 0.8.0+)
|
|
155
|
+
|
|
156
|
+
A terminal for a **human**, sharing the same shell knowledge as the model-facing `shell`/`terminal` tools but a different environment.
|
|
157
|
+
|
|
158
|
+
**Entry point**: the bottom workbench of [dsh-better-sidebar](https://www.npmjs.com/package/dsh-better-sidebar) 0.25+. This plugin registers **its own tab there, "Terminal (multi-shell)"**, which reaches all four shells:
|
|
159
|
+
|
|
160
|
+
| Host | What happens |
|
|
161
|
+
|---|---|
|
|
162
|
+
| Terminal controller present (DSH 0.2.0-rc.2+, official CLI/GUI builds) | The official "Terminal" row is powered by the host and keeps working; this plugin **does not touch** `webTerminals` and only adds its multi-shell tab |
|
|
163
|
+
| No controller mounted (some Desktop builds, where the official row is dead) | The multi-shell tab works regardless. Turn on "Take over the official terminal row" only if you specifically need that official row lit up |
|
|
164
|
+
|
|
165
|
+
Why a tab of our own: the official shell menu **de-duplicates by executable file name** and keeps the earliest entry, so a bare `bash` on Windows resolves to `C:\Windows\System32\bash.exe` - the WSL forwarder - and Git Bash (also `bash.exe`) can never join it. Our tab picks by catalog ROW, so all four coexist. The official row is still there; this package adds exactly one entry to it (see below).
|
|
166
|
+
|
|
167
|
+
> **The take-over switch is off by default, and that is a correctness rule, not caution.** The host's controller client registers the same `webTerminals` name, cordis **refuses a second provider of one name**, and the loser is the one that throws - the controller's provide is unguarded, so if we get there first we take down the first-party terminal and the page becomes `Failed to load plugins / web boot: 1 entry did not activate` (measured on 0.2.0-rc.2). When you do switch it on, the plugin **reads the service live first** and only publishes if the name is free, then states the outcome in the settings row ("taken over" / "the host already publishes it"). So enabling it on a host that has a controller cannot break anything.
|
|
168
|
+
|
|
169
|
+
The tab registers only when `betterSidebar` is available, through `ctx.inject(['betterSidebar'])` so arrival order does not matter (with a soft read as fallback) - and it must never read `ctx.betterSidebar` as a property: the client context is a Proxy and an undeclared service name throws `cannot get property "betterSidebar" without inject`. xterm is **bundled into this plugin's own artifact** - DSH's client module table resolves only platform seed words, shell-owned modules, registered package factories and boot modules, so an external `require("@xterm/xterm")` fails at mount with "missed the module table". The settings section states which path you are on ("The host terminal is in charge").
|
|
170
|
+
|
|
171
|
+
The settings have **two landing points**, and both render the SAME component (a change cannot land on one and miss the other):
|
|
172
|
+
|
|
173
|
+
| Surface | Slot | Host lines |
|
|
174
|
+
|---|---|---|
|
|
175
|
+
| Plugin detail page (bundle settings) | `plugins.bundle.config`, keyed by the package name | 0.1.7-rc.1+ / 0.2.0 |
|
|
176
|
+
| A tab under the family section | `dsh-family.tab` (container: dsh-thinking-levels) | as above, and TL must be installed |
|
|
177
|
+
|
|
178
|
+
Both arms are registered and whichever has a renderer wins. **When there is no family container to carry the card, this plugin registers its own top-level `settings.section`** (id `bash-terminal`; it never shadows `dsh-family`, because two entries under one id produce two navigation rows). That decision is taken when the host *declares* the section - opening the settings panel - not at apply time, because the container belongs to another plugin's client and deciding early means betting on bundle order.
|
|
179
|
+
|
|
180
|
+
Two further arms exist for cross-version reach: `settings.plugin.item` (keyed by the settings namespace) is the 0.1.2/0.1.5 equivalent of the bundle detail card, and the `shell.overlay` panel renders **only when the host itself reports that it has no settings page** (the component returns null otherwise). That classification reads neither a version string nor an imported host package - host packages live in the application's tree and a plugin cannot resolve them - but the capability face of the settings service: `installSection` present → 0.1.2/0.1.5 (page exists); only `register` → 0.1.0/0.1.1 (the `settings.*` slot map is declared but **no page renders it**, so a registered card would be unreachable and the plugin must bring its own entry); neither → 0.1.7+/0.2.0 (page exists).
|
|
181
|
+
|
|
182
|
+
- Activation switches: Git Bash / MSYS2 / WSL. A switch means *allowed*; usable = allowed ∧ detected, so “detected means on” needs no extra configuration. **PowerShell is the floor and cannot be turned off.**
|
|
183
|
+
- `defaultShell` only offers rows that are activated ∧ available, validated server-side; a stale value falls back through `[powershell, gitbash, msys2, wsl]` with a visible notice and is never rewritten, and an empty candidate set falls back to pwsh → powershell.exe → cmd.exe.
|
|
184
|
+
- The browser terminal starts PowerShell **without `-NoProfile`** (your aliases and prompt survive) and with a real terminal type, unlike the tool path, which pins `TERM=dumb`/`NO_COLOR=1` because a parseable one-shot output is correct there.
|
|
185
|
+
- It does **not** add anything for the model: no tool, no system-prompt section, no sandbox approval. Routes are loopback-only (a DNS-rebinding/LAN guard, not authentication).
|
|
186
|
+
- MSYS2 is offered as `usr\bin\bash.exe` with `MSYSTEM=MINGW64`; `msys2.exe`/`git-bash.exe` are refused because they open their own window and return zero bytes on a pipe.
|
|
187
|
+
- What the official menu does change: this package's `cordis.patch.yml` overrides the host `terminal-controller` row's `shellCandidates`, **prepending one entry only** - `C:\msys64\usr\bin\msys-bash.exe`, a hard link that gives MSYS2 a distinct file name so de-duplication stops erasing it. The stock candidates are left untouched. Pinning `Git\bin\bash.exe` ahead of them (an earlier revision did) would claim the `bash` key from the **WSL forwarder** and delete WSL from the menu; and Git cannot be renamed without a link inside `C:\Program Files\Git\bin\`, which needs elevation and must stay inside the Git tree because bash derives its POSIX root from its own path. So Git Bash comes from this plugin's tab instead.
|
|
188
|
+
- Two things that override costs: the patch replaces that row's config **whole**, so all eleven keys are spelled out and `test/webterminals.ts` asserts both the full key set and that every stock candidate survives; and MSYS2's `/usr/bin` is missing in a non-login shell (the host passes `-i`, guessed from the file name, with no env channel), which is why `C:\msys64\home\<user>\.bashrc` needs an idempotent `PATH="/usr/bin:$PATH"` guard. Measured on the live host: `cmd.exe | msys-bash.exe | bash(=System32, WSL) | powershell`.
|
|
189
|
+
|
|
190
|
+
Details, the argv/env table and the measured smoke output are in `README.md` (Chinese), which is the maintained copy.
|
|
191
|
+
|
|
94
192
|
## Config
|
|
95
193
|
|
|
96
|
-
Web UI: Settings -> General -> Default terminal. Plugin row `config` overrides: `defaultShell`, `timeoutMs`, `maxTimeoutMs`, `pwshPath`, `gitBashPath`, `msys2Path`, `wslPath`.
|
|
194
|
+
Web UI: Settings -> General -> Default terminal. Plugin row `config` overrides: `defaultShell`, `timeoutMs`, `maxTimeoutMs`, `pwshPath`, `gitBashPath`, `msys2Path`, `wslPath`, plus the bottom-terminal fields `shellGitBash`, `shellMsys2`, `shellWsl`, `wslDistro`.
|
|
97
195
|
|
|
98
196
|
## Uninstall
|
|
99
197
|
|
package/README.md
CHANGED
|
@@ -113,6 +113,63 @@ New-Item -ItemType Junction -Path "D:\workspace\projects\dsh-bash-terminal-ts\no
|
|
|
113
113
|
node "$env:APPDATA\nvm\<node-version>\node_modules\@deepseek-ai\dsh\lib\bin.js" --profile web --dump-config | Select-String dsh-bash-terminal-ts
|
|
114
114
|
```
|
|
115
115
|
|
|
116
|
+
## shell 别名:让宿主官方终端也列出 Git Bash / MSYS2(可选)
|
|
117
|
+
|
|
118
|
+
本插件自带的「终端(多 shell)」tab 不依赖任何东西,四种 shell 开箱即用。但宿主**官方**右栏终端的 shell 菜单是按**可执行文件名**去重的(`discoverShells` 拿 basename 当 Map 键、保留最早一条),而 Git Bash 与 MSYS2 都叫 `bash.exe` —— 二者在官方菜单里最多只能出现一个。
|
|
119
|
+
|
|
120
|
+
包内 `cordis.patch.yml` 因此给官方终端追加两个**带自己文件名**的候选:`C:\msys64\usr\bin\msys-bash.exe` 和 `C:\Program Files\Git\bin\git-bash.exe`。它们是硬链接(同一 inode,不占空间,删掉即恢复原状),**必须机器上真存在**才有条目;不存在时宿主按候选逐个跳过,菜单与改动前完全一致,不报错。
|
|
121
|
+
|
|
122
|
+
建别名(任选一条路径):
|
|
123
|
+
|
|
124
|
+
```powershell
|
|
125
|
+
# A. 包内脚本:能建的就建,建不了的原样打印出管理员命令
|
|
126
|
+
node scripts/link-shells.mjs
|
|
127
|
+
|
|
128
|
+
# B. 手工建(MSYS2 那条普通权限即可;Git 那条要在**管理员** PowerShell / cmd 里)
|
|
129
|
+
cmd /c mklink /H "C:\msys64\usr\bin\msys-bash.exe" "C:\msys64\usr\bin\bash.exe"
|
|
130
|
+
cmd /c mklink /H "C:\Program Files\Git\bin\git-bash.exe" "C:\Program Files\Git\bin\bash.exe"
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
手上没有现成的管理员终端时,从普通权限的会话里**拉一个起来**(会弹一次 UAC,点“是”):
|
|
134
|
+
|
|
135
|
+
```powershell
|
|
136
|
+
Start-Process cmd -Verb RunAs -Wait -ArgumentList '/c','mklink /H "C:\Program Files\Git\bin\git-bash.exe" "C:\Program Files\Git\bin\bash.exe" > "%TEMP%\mklink.log" 2>&1'
|
|
137
|
+
Get-Content "$env:TEMP\mklink.log"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
别名与原件行为完全一致(实测 `git-bash.exe -i`:`MSYSTEM=MINGW64`、POSIX 根 `C:\Program Files\Git\`、`$0=/usr/bin/bash`、git 可用)—— 因为链接就待在 `Git\bin\` 里,bash 按自身路径推根时拿到的仍是同一个根。
|
|
141
|
+
|
|
142
|
+
Git 那条**绕不开提权**,两个独立原因:`CreateHardLink` 要求对**源文件**有写权限(Program Files 对标准用户拒绝,所以链接也挪不到别的目录去建);而链接又必须留在 `Git\bin\` 里 —— bash 按自身路径推导它的 POSIX 根,换目录就找不到 `/usr/bin`、`/etc`。
|
|
143
|
+
|
|
144
|
+
> **交给 agent 代劳** —— 把下面这段原话贴给一个以完全权限(管理员)运行的 agent:
|
|
145
|
+
>
|
|
146
|
+
> ```text
|
|
147
|
+
> 请以管理员权限为 DSH 宿主官方终端创建两个 shell 别名硬链接:
|
|
148
|
+
> cmd /c mklink /H "C:\msys64\usr\bin\msys-bash.exe" "C:\msys64\usr\bin\bash.exe"
|
|
149
|
+
> cmd /c mklink /H "C:\Program Files\Git\bin\git-bash.exe" "C:\Program Files\Git\bin\bash.exe"
|
|
150
|
+
> 目标文件已存在时跳过创建、不要覆盖。然后跑本包内的 node scripts/link-shells.mjs 校验,
|
|
151
|
+
> 两条都应输出 ok ... (same inode, nlink=2)。最后重启 DSH 让官方终端重新发现 shell。
|
|
152
|
+
> 约束:不要复制文件、不要建符号链接(symbolic link)、不要把链接放到其它目录、
|
|
153
|
+
> 不要改动 bash.exe 本身。若提权失败,把确切的命令和错误码回报给我,不要改用拷贝兜底。
|
|
154
|
+
> ```
|
|
155
|
+
|
|
156
|
+
改完重启 DSH,官方终端「新建」下拉应出现 `msys-bash.exe` / `git-bash.exe`,同时 `bash`(Windows 上解析到 WSL 转发器 `C:\Windows\System32\bash.exe`)**必须仍然在列** —— 本包不会为了 Git 去占 `bash` 这个键而把 WSL 挤出菜单。
|
|
157
|
+
|
|
158
|
+
> ⚠️ **MSYS2 还差一步:`~/.bashrc` 的 PATH 守卫**(`C:\msys64\home\<你>\.bashrc`)。
|
|
159
|
+
> 宿主把每个候选都当**交互式非登录** shell 起(`shellCandidates` 是纯字符串数组,传不了 `-l`),而非登录 shell 不会 source `/etc/profile` —— MSYS2 的 `/usr/bin` 正是由它加进 PATH 的。不补这一步,`msys-bash.exe` 能开、但 `uname`/`ls` 一律「未找到命令」。
|
|
160
|
+
> Git Bash **不需要**这个守卫(它自己就会把 `/usr/bin`、`/mingw64/bin` 注入 PATH,实测 `cygpath`、`git` 直接可用)。
|
|
161
|
+
>
|
|
162
|
+
> ```bash
|
|
163
|
+
> # >>> dsh-bash-terminal-ts PATH guard >>>
|
|
164
|
+
> case ":$PATH:" in
|
|
165
|
+
> *":/usr/bin:"*) ;;
|
|
166
|
+
> *) PATH="/usr/bin:$PATH" ;;
|
|
167
|
+
> esac
|
|
168
|
+
> # <<< dsh-bash-terminal-ts PATH guard <<<
|
|
169
|
+
> ```
|
|
170
|
+
>
|
|
171
|
+
> 前置在这里是安全的:`/usr/bin/gcc.exe` 不存在,mingw64 工具链仍然先命中(实测 `command -v gcc` 仍为 `/mingw64/bin/gcc`)。但**跑过 `pacman -S mingw-w64-*` 之后要重测一次**,否则可能反过来抢掉编译器。回滚:删掉两段哨兵注释之间的内容。
|
|
172
|
+
|
|
116
173
|
## 使用
|
|
117
174
|
|
|
118
175
|
**用户在 Web UI 设置默认终端**:打开设置(齿轮)→ 通用 →「默认终端」下拉,选择 PowerShell / Git Bash / MSYS2 / WSL 之一。改动即时生效并持久化。
|
|
@@ -210,7 +267,142 @@ powershell -ExecutionPolicy Bypass -File install.ps1 uninstall
|
|
|
210
267
|
- WSL 后台进程在超时/中断后可能在发行版内短暂残留(WSL 实例在最后一个进程退出后自动关闭)。
|
|
211
268
|
- Git Bash 是 msys2 环境,与 WSL 的 Linux 行为存在差异(路径映射、包可用性)。
|
|
212
269
|
- MSYS2 后端需要本机安装 MSYS2(默认 `C:\msys64`);未安装时 `shell` 报 `backend unavailable`,可用 `msys2Path` 指定自定义位置。候选顺序永远是 `bash.exe` 优先、`msys2.exe` 兜底——`msys2.exe` 在管道 stdio 下会静默返回零字节,仅作最后手段。
|
|
213
|
-
-
|
|
270
|
+
- 本插件的 `shell` / `terminal` 工具仅在 `win32` 平台注册;**底部终端不限制平台**(它在 win32 用 node-pty,在其他平台用宿主 `ctx.subprocess.spawnTerminal`)。
|
|
271
|
+
|
|
272
|
+
## 底部终端(浏览器内嵌 shell,0.8.0+)
|
|
273
|
+
|
|
274
|
+
一个面向**人**的终端,跟面向模型的 `shell`/`terminal` 工具共用同一套 shell 知识,但环境完全不同。
|
|
275
|
+
|
|
276
|
+
**入口**:[dsh-better-sidebar](https://www.npmjs.com/package/dsh-better-sidebar) 0.25+ 的底部工作台。本插件在那里注册**自己的 tab「终端(多 shell)」**,四个 shell 都能用:
|
|
277
|
+
|
|
278
|
+
| 宿主 | 会发生什么 |
|
|
279
|
+
|---|---|
|
|
280
|
+
| 自带终端控制器(DSH 0.2.0-rc.2+,官方 CLI/GUI 构建) | 官方那行「终端」由宿主供电、照常可用;本插件**不碰** `webTerminals`,只额外提供自己的多 shell tab |
|
|
281
|
+
| 没挂控制器(部分 Desktop 构建,官方那行是死的) | 多 shell tab 照常工作。若你**确实**需要点亮官方那行,再往下面打开「接管官方终端行」开关 |
|
|
282
|
+
|
|
283
|
+
为什么要自己的 tab:官方终端菜单按**可执行文件名去重**(`shellKind` = 文件名去 `.exe` 转小写),而 Git Bash 与 MSYS2 的主 shell 同名 `bash.exe`,在默认候选下只能出现一个(本包用一步硬链接绕过了它,见下文)。本插件的 tab 按 catalog **行**选 shell,所以四个能并存;官方那行仍在原地,互不遮蔽。
|
|
284
|
+
|
|
285
|
+
> **「接管官方终端行」默认关闭,这不是保守而是必需**。`webTerminals` 这个名字宿主的控制器 client 半也会注册,而 cordis **拒绝同名二次 provide**、抛错的是后到的一方——控制器的 provide 没有保护,所以我们先到就会把**官方终端整个打挂**,页面变成 `Failed to load plugins / web boot: 1 entry did not activate`(实测于 0.2.0-rc.2)。开关打开时本插件会**先活读一次服务**,确认名字没人占才提供,并在设置行里明说结果(「已接管」/「宿主已提供,未接管」)。也就是说:这个开关只在没有控制器的部署上有意义,在有控制器的宿主上打开它也不会造成破坏。
|
|
286
|
+
|
|
287
|
+
这个 tab 只在 `ctx.betterSidebar` 存在时注册,走 `ctx.inject(['betterSidebar'])` 到达即注册(软读兜底)——**不能属性读** `ctx.betterSidebar`:client ctx 是 Proxy,读未声明的服务名会直接抛 `cannot get property "betterSidebar" without inject`。xterm **打包进本插件自己的 bundle**(DSH 的 client 模块表只认平台 seed words / shell 自有模块 / 已登记包工厂 / boot 模块四类,第三方包不在其中,外部 `require("@xterm/xterm")` 会在挂载时报 missed the module table)。设置节会明说当前是哪条路径(「当前由宿主终端提供」)。
|
|
288
|
+
|
|
289
|
+
设置面一共**两个落点**,两处渲染同一个组件(不会一边改了另一边没改):
|
|
290
|
+
|
|
291
|
+
| 落点 | 插槽 | 适用宿主线 |
|
|
292
|
+
|---|---|---|
|
|
293
|
+
| 插件详情页(bundle 设置) | `plugins.bundle.config`(按包名 `dsh-bash-terminal-ts` 派发) | 0.1.7-rc.1+ / 0.2.0 |
|
|
294
|
+
| 家族节「起子插件设置」下的 tab | `dsh-family.tab`(容器 = dsh-thinking-levels) | 同上,且需安装 TL |
|
|
295
|
+
|
|
296
|
+
两个注册臂都在,哪个有渲染宿主哪个就生效。**没有可寄生的家族节时,本插件自己注册一个顶级 `settings.section`**(id `bash-terminal`,永远不冒用 `dsh-family`——同 id 遮蔽位会出双导航行)。这个判定发生在**宿主声明 section 的那一刻**(设置面板打开),不是 apply 期——容器是另一个插件的 client,apply 期赌的就是 bundle 顺序。
|
|
297
|
+
|
|
298
|
+
另外两个臂是为了跳版本:`settings.plugin.item`(key = 设置命名空间)是 0.1.2/0.1.5 上相当于 bundle 详情页的那个面;`shell.overlay` 浮窗**只在宿主自己报「没有设置页」时渲染**(组件其余时候 return null)。该判定不读版本号、也不依赖 import 宿主包(宿主包在 app 树里,插件目录根本解析不到),而是看 settings 服务实例的能力面:`installSection` 在 → 0.1.2/0.1.5(有页);只有 `register` → 0.1.0/0.1.1(**槽位表在但没有页渲染它**,所以卡片注册了也打不开,只能自带入口);两者都不在 → 0.1.7+/0.2.0(有页)。
|
|
299
|
+
|
|
300
|
+
### 开关与默认 shell
|
|
301
|
+
|
|
302
|
+
| 项 | 默认 | 语义 |
|
|
303
|
+
|---|---|---|
|
|
304
|
+
| Git Bash | 开 | 开关 = 允许;**实际可用 = 允许 ∧ 本机探到**,所以“探到即开”无需额外配置 |
|
|
305
|
+
| MSYS2 | 开 | 同上;候选永远是 `usr\bin\bash.exe` 优先,`msys2.exe` 拒用(它开自己的窗口,管道零字节) |
|
|
306
|
+
| WSL bash | 开 | 同上;`wslDistro` 为空用 WSL 默认发行版 |
|
|
307
|
+
| PowerShell | 恒开 | **保底后端,不可关闭**(关掉保底就不叫保底) |
|
|
308
|
+
|
|
309
|
+
`defaultShell` 下拉只列「已激活 ∧ 可用」项,服务端二次校验(越界回 `bad-request`)。分配时的回落链:
|
|
310
|
+
|
|
311
|
+
```
|
|
312
|
+
候选 = 已激活 ∧ 可用,顺序 [powershell, gitbash, msys2, wsl]
|
|
313
|
+
stored == "" → 取候选首项,不提示(“用户未选”不是“选错了”)
|
|
314
|
+
stored ∈ 候选 → 用它
|
|
315
|
+
stored 越界 → 取候选首项 + 可见提示(不回写存值)
|
|
316
|
+
候选为空 → pwsh → powershell.exe → cmd.exe,提示“已兜底”
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### UI 终端与模型工具的环境差异(为什么不能复用)
|
|
320
|
+
|
|
321
|
+
| shell | 模型工具 argv | 底部终端 argv | 底部终端 env |
|
|
322
|
+
|---|---|---|---|
|
|
323
|
+
| powershell | `-NoLogo -NoProfile [-NonInteractive -Command <cmd>]` | **`-NoLogo`**(保留用户 profile) | `process.env` + `TERM=xterm-256color` |
|
|
324
|
+
| gitbash | `-lc <cmd>` | `-i` | 同上,不注入 MSYSTEM(Git 自己会设 `MINGW64`) |
|
|
325
|
+
| msys2 | `-lc <cmd>` | **`-l`**(绝不 `-lc`:无操作数会 exit 2) | 同上 + `MSYSTEM=MINGW64`(显式值赢) |
|
|
326
|
+
| wsl | `[-d <d>] -e bash -lc <cmd>` | 有 distro `-d <d> -e bash -i`;无 `-- bash -i` | 同上 |
|
|
327
|
+
|
|
328
|
+
工具路径额外钉 `TERM=dumb`/`NO_COLOR=1`/`PAGER=cat`,这对一次性命令正确,对用户看到的屏幕错误(无颜色、无光标控制、无 profile)。两者共用同一个 node-pty 分配器(`spawnPtyHandle(spec, style)`),只是 `style` 不同 —— 写两份就会让其中一份慢慢漂到错的终端类型上。
|
|
329
|
+
|
|
330
|
+
另:ConPTY 下 bash 的 `$TERM` 实测为空(终端类型靠 node-pty 的 `name` 选项传递),所以 `TERM=xterm-256color` 主要作用于 POSIX 缝路径。
|
|
331
|
+
|
|
332
|
+
### 官方终端菜单也被改了(以及为什么需要两步机器前置)
|
|
333
|
+
|
|
334
|
+
本包的 `cordis.patch.yml` 覆盖宿主 `terminal-controller` 那一行的 `shellCandidates`,**只在原生列表前插一项**:
|
|
335
|
+
|
|
336
|
+
```yaml
|
|
337
|
+
- id: terminal-controller
|
|
338
|
+
config:
|
|
339
|
+
shellCandidates:
|
|
340
|
+
- 'C:\msys64\usr\bin\msys-bash.exe' # 硬链接,kind 不同 → 新增一项
|
|
341
|
+
- zsh
|
|
342
|
+
- bash # ← 保留:Windows 上它解析到 WSL 转发器
|
|
343
|
+
- fish
|
|
344
|
+
- pwsh
|
|
345
|
+
- powershell
|
|
346
|
+
- cmd
|
|
347
|
+
# …以及其余 10 个限制键,均照宿主默认值写全
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
**为什么不能把 `Git\bin\bash.exe` 也排到前面**(上一版就是这么写的,已回退):`discoverShells` 按可执行文件名去重且**保留最早一条**,而 Windows 上裸名 `bash` 解析到的是 `C:\Windows\System32\bash.exe`——**WSL 转发器**。把 Git 排在它前面等于把 WSL 从官方菜单里**删掉**,这不是任何人的需求。Git Bash 拿不到自己的 key 是因为改名需要往 `C:\Program Files\Git\bin\`(需管理员权限)里建硬链接,而且该链接必须留在 Git 树内——Cygwin/Git-bash 按可执行文件所在路径推导它的 POSIX 根。所以 **Git Bash 由本插件自己的 tab 提供**,官方行只动了一处:把被去重抹掉的 MSYS2 加回来。
|
|
351
|
+
|
|
352
|
+
实测(`POST /api/terminal/shells`,同一沙箱实例):
|
|
353
|
+
|
|
354
|
+
```
|
|
355
|
+
cmd.exe=C:\Windows\system32\cmd.exe | msys-bash.exe=C:\msys64\usr\bin\msys-bash.exe
|
|
356
|
+
bash=C:\Windows\system32\bash.EXE | powershell=...\powershell.EXE
|
|
357
|
+
→ WSL bash retained AND MSYS2 added
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
两个必须知道的风隙:
|
|
361
|
+
|
|
362
|
+
1. **整块替换**。patch 对该行是**全量替换 config**,少写一个键就是静默删掉那个限制(`.default()` 只在输入缺字段时生效)。所以十一个键全部显式写出,并由 `test/webterminals.ts` 断言键集完整**且六个原生候选一个不少**——以后谁再把绝对路径的 `bash.exe` 插到裸名前面,构建会直接失败。
|
|
363
|
+
2. **去重键是文件名**。所以 MSYS2 需要一个不同名的入口:
|
|
364
|
+
|
|
365
|
+
```powershell
|
|
366
|
+
# 前置一:给 MSYS2 一个不同名的硬链接(同 inode,不占空间,删文件即回滚)
|
|
367
|
+
New-Item -ItemType HardLink -Path C:\msys64\usr\bin\msys-bash.exe -Target C:\msys64\usr\bin\bash.exe
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
# 前置二:非登录 shell 不 source /etc/profile,/usr/bin 会缺(uname/ls 报“未找到命令”)
|
|
372
|
+
# 宿主给候选的参数是按文件名猜死的 `-i`,插件无法改成 `-l`、也没有 env 通道,
|
|
373
|
+
# 所以这一段必须落在 MSYS2 自己的 ~/.bashrc 里(幂等,删 sentinel 之间即回滚)
|
|
374
|
+
case ":$PATH:" in
|
|
375
|
+
*":/usr/bin:"*) ;;
|
|
376
|
+
*) PATH="/usr/bin:$PATH" ;;
|
|
377
|
+
esac
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
实测(真 node-pty,完全复现宿主的启动方式):
|
|
381
|
+
|
|
382
|
+
| 条目 | `uname` / `ls` | `gcc` |
|
|
383
|
+
|---|---|---|
|
|
384
|
+
| `bash.exe`(Git Bash) | ✅ | —(Git 本身不带编译器) |
|
|
385
|
+
| `msys-bash.exe`(两项前置后) | ✅ | ✅ `/mingw64/bin` |
|
|
386
|
+
| `msys-bash.exe`(缺前置二) | ❌ MISSING | ✅ |
|
|
387
|
+
|
|
388
|
+
前置二用**前置** `/usr/bin` 在本机是安全的:`/usr/bin/gcc.exe` 不存在,所以 mingw64 工具链仍然优先;但如果你日后 `pacman -S mingw-w64-x86_64-gcc` 装了 MSYS 层级的 gcc,需要重新测一遍优先级。
|
|
389
|
+
|
|
390
|
+
### 边界
|
|
391
|
+
|
|
392
|
+
- 两条 HTTP 路由(legacy `/api/bash-terminal/config` 与新的 `/api/bash-terminal/terminals`)+ 一条 WS(`/bash-terminal/ws`),均**仅限回环地址**(防 DNS rebinding / 局域网,不是认证)。
|
|
393
|
+
- **不给模型新增任何能力**:不注册工具、不写 systemPrompt 段落、不碰沙箱审批。
|
|
394
|
+
- 生命周期沿用官方语义:关掉标签才终止进程;退出的终端仍占配额直到关闭;刷新页面靠 grace 重接同一 shell。
|
|
395
|
+
|
|
396
|
+
### 实测记录(win32 + 真 node-pty)
|
|
397
|
+
|
|
398
|
+
```
|
|
399
|
+
seam backend = node-pty | socket mount = mounted
|
|
400
|
+
PASS powershell powershell.exe argv=[-NoLogo] resized=true
|
|
401
|
+
PASS gitbash bash.exe argv=[-i] resized=true
|
|
402
|
+
PASS msys2 bash.exe argv=[-l] msys=MINGW64 uname=MINGW64_NT-10.0-26300
|
|
403
|
+
PASS wsl wsl.exe argv=[-- bash -i] uname=Linux
|
|
404
|
+
terminals after close: 0
|
|
405
|
+
```
|
|
214
406
|
|
|
215
407
|
## 测试
|
|
216
408
|
|
|
@@ -218,7 +410,8 @@ powershell -ExecutionPolicy Bypass -File install.ps1 uninstall
|
|
|
218
410
|
cd D:\workspace\projects\dsh-bash-terminal-ts
|
|
219
411
|
npm install # 安装依赖(含 typescript)
|
|
220
412
|
npm run build # tsc 编译 src/*.ts → lib/*.js;client.tsx → lib/client.js + dist/client.js;test/*.ts → test-dist/
|
|
221
|
-
npm test #
|
|
413
|
+
npm test # unit / apply / client / terminal / shells / webterminals
|
|
414
|
+
node _smoke-ui.mjs # 真机冒烟:四类交互 shell 走真 node-pty + 真 WebSocket
|
|
222
415
|
```
|
|
223
416
|
|
|
224
417
|
源码为 TypeScript(`strict` + `noUncheckedIndexedAccess`),编译产物 `lib/`、`dist/` 随仓库提交,DSH 直接按 `lib/index.js` 加载,无需安装即可使用。
|
package/cordis.patch.yml
CHANGED
|
@@ -3,3 +3,69 @@
|
|
|
3
3
|
- insert:
|
|
4
4
|
- id: tool-bash-terminal
|
|
5
5
|
name: 'dsh-bash-terminal-ts'
|
|
6
|
+
|
|
7
|
+
# ---------------------------------------------------------------------------
|
|
8
|
+
# The host's own right-sidebar terminal: which shells its selector offers.
|
|
9
|
+
#
|
|
10
|
+
# Why a plugin touches this at all (user decision, 2026-10-13, overriding the
|
|
11
|
+
# original "never modify the host terminal row" rule): the official menu builds
|
|
12
|
+
# its list from `shellCandidates` through `discoverShells`, which keys a Map by
|
|
13
|
+
# the executable FILE NAME (`shellKind` = basename, lower-cased, `.exe`
|
|
14
|
+
# stripped) and keeps the earliest entry. Git Bash and MSYS2 are both
|
|
15
|
+
# `bash.exe`, so on the stock list MSYS2 can never appear. A hard link at
|
|
16
|
+
# `C:\msys64\usr\bin\msys-bash.exe` gives it a distinct kind; measured over a
|
|
17
|
+
# real PTY, bash does not mis-classify itself under the new name
|
|
18
|
+
# (`argv0=/usr/bin/msys-bash`, `$BASH_VERSION` 5.3.15, `gcc` resolving to
|
|
19
|
+
# /mingw64/bin, correct `-dumpmachine`).
|
|
20
|
+
#
|
|
21
|
+
# Why all twelve keys are spelled out: a patch replaces the targeted row's WHOLE
|
|
22
|
+
# config. Omit one and that limit silently reverts to nothing at all - each
|
|
23
|
+
# field's `.default()` applies when the field is absent from the schema input,
|
|
24
|
+
# and a replaced row provides no input for the keys it does not mention.
|
|
25
|
+
# `scripts/check-artifacts.mjs` asserts the full key set, so a host upgrade that
|
|
26
|
+
# adds a thirteenth key fails the build instead of quietly dropping a limit.
|
|
27
|
+
#
|
|
28
|
+
# Which entries are prepended, and why nothing is displaced: `discoverShells` keeps the
|
|
29
|
+
# EARLIEST entry per executable file name, and on Windows a bare `bash` resolves to
|
|
30
|
+
# `C:\Windows\System32\bash.exe` - the WSL forwarder. Listing `Git\bin\bash.exe` here (an
|
|
31
|
+
# earlier revision did exactly that) would claim that key for Git and silently DELETE WSL
|
|
32
|
+
# from the menu. Both additions therefore carry their own file name: `msys-bash.exe` is a
|
|
33
|
+
# hard link in the user-writable MSYS2 tree, and `git-bash.exe` is a hard link inside
|
|
34
|
+
# `C:\Program Files\Git\bin\`, which needs one elevated step by the operator:
|
|
35
|
+
#
|
|
36
|
+
# New-Item -ItemType HardLink -Path 'C:\Program Files\Git\bin\git-bash.exe' -Target 'C:\Program Files\Git\bin\bash.exe'
|
|
37
|
+
#
|
|
38
|
+
# (It has to live in that directory: bash derives its POSIX root from its own path, and a
|
|
39
|
+
# link elsewhere cannot even be created - CreateHardLink needs write access to the source
|
|
40
|
+
# file, which Program Files denies a standard user.) Until that link exists the candidate
|
|
41
|
+
# is skipped, so the row is correct either way and the stock candidates stay untouched.
|
|
42
|
+
#
|
|
43
|
+
# `shell` is deliberately absent: that means "no explicit default", so the
|
|
44
|
+
# controller uses the execution environment's default shell - the stock
|
|
45
|
+
# behaviour, unchanged.
|
|
46
|
+
#
|
|
47
|
+
# Degradation: `discoverShells` catches SubprocessExecutableNotFoundError per
|
|
48
|
+
# candidate, so a machine without Git, or without the link, simply offers fewer
|
|
49
|
+
# shells. This row cannot fail.
|
|
50
|
+
# ---------------------------------------------------------------------------
|
|
51
|
+
- id: terminal-controller
|
|
52
|
+
config:
|
|
53
|
+
shellCandidates:
|
|
54
|
+
- 'C:\Program Files\Git\bin\git-bash.exe'
|
|
55
|
+
- 'C:\msys64\usr\bin\msys-bash.exe'
|
|
56
|
+
- zsh
|
|
57
|
+
- bash
|
|
58
|
+
- fish
|
|
59
|
+
- pwsh
|
|
60
|
+
- powershell
|
|
61
|
+
- cmd
|
|
62
|
+
maxTerminals: 8
|
|
63
|
+
maxCols: 500
|
|
64
|
+
maxRows: 200
|
|
65
|
+
scrollback: 1000
|
|
66
|
+
maxBufferedBytes: 2097152
|
|
67
|
+
maxInputBytes: 65536
|
|
68
|
+
disposeGraceMs: 1000
|
|
69
|
+
unattendedTimeoutMs: 7200000
|
|
70
|
+
activityPollIntervalMs: 30000
|
|
71
|
+
cleanupRetryMs: 60000
|