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.md CHANGED
@@ -1,194 +1,197 @@
1
- # dsh-bash-terminal-ts
2
-
3
- [![test](https://github.com/drscrewdriver/dsh-bash-terminal-ts/actions/workflows/test.yml/badge.svg)](https://github.com/drscrewdriver/dsh-bash-terminal-ts/actions/workflows/test.yml)
4
-
5
- [English](README.en.md) | [日本語](README.ja.md) | [한국어](README.ko.md) | **中文**
6
-
7
- DSH(DeepSeek Harness)插件:一个 `shell` 工具,在 Windows 上统一执行 **PowerShell / Git Bash / MSYS2 / WSL** 四种终端命令。
8
-
9
- 终端由**你在 Web UI 里选**,模型不能改。选一次,之后所有命令都按你选的终端跑。
10
-
11
- ## 为什么用它
12
-
13
- | 卖点 | 一句话 |
14
- |------|--------|
15
- | **MSYS2 真的能用** | 不是"加了个下拉选项",而是把 `bash.exe` 解析、登录 shell、`MSYSTEM` 环境三件事都做对了 —— 选 MSYS2 就能直接 `gcc`、`make`(见下方「MSYS2 支持」) |
16
- | **TypeScript 源码** | `strict` + `noUncheckedIndexedAccess`;纯函数负责 argv/env 构造,可单测 |
17
- | **以 DSH 0.1.2 为主版** | `engines.dsh: >=0.1.2-rc.1 <0.2.0-0`,随 0.1.2 的 `ctx.subprocess` / `ctx.sandbox` / PTY 接缝构建 |
18
- | **四种终端一个工具** | 同一套 `shell` 工具覆盖 PowerShell / Git Bash / MSYS2 / WSL,模型不用学四套参数 |
19
- | **沙箱对齐官方** | 走官方 `ctx.sandboxPolicy` + `ctx.sandbox`,fail-closed,拒绝时给出同轮升级提示 |
20
- | **交互式终端** | 另有真 PTY 会话工具:`open / send / read / signal / close`,可 Ctrl+C,跨轮保持状态 |
21
-
22
- ## 支持的后端
23
-
24
- | 后端 | 实际执行 | 语法 / 路径 | 环境变量 |
25
- |------|----------|-------------|----------|
26
- | `powershell`(默认) | `pwsh -NoLogo -NoProfile -NonInteractive -Command <cmd>` | PowerShell;`C:\...` | `$env:NAME` |
27
- | `gitbash` | Git for Windows `bash -lc <cmd>` | POSIX;`/d/WorkSpace`;PATH 含 `/usr/bin`、`/mingw64/bin` | `$NAME` |
28
- | `msys2` | `C:\msys64\usr\bin\bash.exe -lc <cmd>`(登录 shell;`MSYSTEM=MINGW64`) | POSIX;自带完整 GCC / mingw64 工具链 | `$NAME` |
29
- | `wsl` | `wsl [-d <distro>] -e bash -lc <cmd>` | Linux;`/mnt/d/...` | `$NAME`(经 WSLENV) |
30
-
31
- 每次调用都启动全新 shell:**不保留状态**(cwd / 变量 / 别名)—— 请传 `workdir` 而不是用 `cd`。需要跨轮保持状态时用交互式终端工具。
32
-
33
- ## MSYS2 支持
34
-
35
- MSYS2 看着只是"再加一个后端",实际有三个坑,插件逐个处理了:
36
-
37
- **1. 不能启动 `msys2.exe`。**
38
- `C:\msys64\msys2.exe` 是分配控制台窗口的 Cygwin 启动器。本插件用管道 stdio spawn(这是 DSH 的标准方式),此时它会 **exit 0 返回零字节输出** —— 命令静默失败,看起来"成功"了但什么都没做。所以候选顺序是:`usr\bin\bash.exe` → `bin\bash.exe` → `msys2.exe` 兜底,**可用的 `bash.exe` 永远优先**。
39
-
40
- **2. `-lc` 不能省。**
41
- 只有登录 shell 会读 `/etc/profile`,而只有 `/etc/profile` 会把 `/usr/bin` 与 `/mingw64/bin` 加进 PATH。用裸 `-c` 的话 `tr`、`sed`、`gcc` 全是 `command not found`。
42
-
43
- **3. 要注入 `MSYSTEM=MINGW64`。**
44
- 否则 `/etc/profile` 按默认 MSYS 环境初始化,`/mingw64/bin` 里的 gcc、make 不可用。插件经 `buildEnv` 注入(**你显式设置的值优先**),并且 `shell` 工具与交互式终端走的是同一个 `buildEnv` —— 不会出现"工具能跑、终端不能跑"的偏差。
45
-
46
- 实测(真 PTY):prompt 从 `MSYS` 变成 `MINGW64`,`command -v gcc` → `/mingw64/bin/gcc`。
47
-
48
- 这三条都有回归测试守着:`test/unit.ts` 断言 `bash.exe` 排在 `msys2.exe` 之前;`test/apply.ts` 断言 PTY 环境含 `MSYSTEM=MINGW64`,且不会泄漏到 gitbash。
49
-
50
- ## 设计要点
51
-
52
- - **终端由用户决定,AI 无法更改**:Web UI 设置页(设置 → 通用)出现"默认终端"下拉(PowerShell / Git Bash / MSYS2 / WSL);`shell` 工具永远只使用该设置,不暴露终端参数给模型。设置通过 DSH settings 系统持久化(settings.yaml)。
53
- - **不占用 `ctx.shell` 能力接缝**:DSH 自带的沙箱化 `pwsh` 工具保持原样可用;本插件的 `shell` 工具是**额外的**多终端入口。
54
- - 通过共享的 `ctx.subprocess` seam 派生进程:进程树终止(Windows `taskkill /T`)、SIGTERM→grace→SIGKILL、输出 spill 文件,与官方 `dsh-tool-bash` / `dsh-tool-pwsh` 行为一致。
55
- - 后台任务注册进通用 `jobs` registry,支持 `run_in_background` / `job_output` / `job_kill`。
56
- - 工具参数 `shell` 是枚举(UI 自动渲染为下拉),模型每次调用自行选择终端。
57
- - 四种后端在前端下拉里的 `<option>`、以及两种语言包里的 `shell.<id>` 文案,都有 drift 守卫测试 —— 加后端忘了改文案会直接测试失败。
58
-
59
- ## 安装
60
-
61
- ### 标准安装(npm)
62
-
63
- ```powershell
64
- # 1. 安装插件包
65
- npm install -g dsh-bash-terminal-ts
66
- dsh plugin --profile web add dsh-bash-terminal-ts
67
-
68
- # 2. patch DSH 设置白名单(DSH 限制,见下方说明;install.ps1 可单独执行此步)
69
- powershell -ExecutionPolicy Bypass -File install.ps1 install
70
-
71
- # 3. 重启 dsh web
72
- ```
73
-
74
- ### 本地开发安装(junction 直连,改源码即时生效)
75
-
76
- ```powershell
77
- # 1. 链接插件包到 profile 的 node_modules(junction,改源码即时生效)
78
- $profile = "$env:USERPROFILE\.dsh\profiles\web"
79
- New-Item -ItemType Junction -Path "$profile\node_modules\dsh-bash-terminal-ts" -Target "D:\WorkSpace\projects\dsh-bash-terminal-ts" | Out-Null
80
-
81
- # 2. 让插件能解析 @deepseek-ai/* 依赖(junction 到 profile 的依赖树)
82
- New-Item -ItemType Junction -Path "D:\WorkSpace\projects\dsh-bash-terminal-ts\node_modules\@deepseek-ai" -Target "$profile\..\node_modules\@deepseek-ai" | Out-Null
83
-
84
- # 3. 在 cordis.patch.yml 追加挂载行(见下方 patch 片段)
85
- # 4. (仅修改前端源码后)重新打包 client bundle:
86
- # cd D:\WorkSpace\projects\dsh-bash-terminal-ts && node scripts/build-client.mjs
87
- # 5. 让设置 UI 接受本插件的设置写入(DSH 限制,见下方说明)
88
- # 6. 重启 dsh web
89
- ```
90
-
91
- > **DSH 设置 UI 白名单限制**:DSH 的 api-gateway(dsh-host-apiproxy)对
92
- > Web 设置客户端暴露的 settings namespace 有**硬编码白名单**(第三方插件
93
- > 的设置默认会被 `settings-not-exposed` 拒绝,UI 里改了不生效)。
94
- > install.ps1 会自动 patch 该白名单(加入 `bash-terminal`,先备份原文件)。
95
- > **升级 DSH 后需重新运行 install.ps1** 恢复 patch。卸载时 install.ps1 会还原。
96
-
97
- `cordis.patch.yml` 追加:
98
-
99
- ```yaml
100
- - insert:
101
- - id: tool-bash-terminal
102
- name: 'dsh-bash-terminal-ts'
103
- ```
104
-
105
- 验证组合树(无需重启):
106
-
107
- ```powershell
108
- node "$env:APPDATA\nvm\v24.16.0\node_modules\@deepseek-ai\dsh\lib\bin.js" --profile web --dump-config | Select-String dsh-bash-terminal-ts
109
- ```
110
-
111
- ## 使用
112
-
113
- **用户在 Web UI 设置默认终端**:打开设置(齿轮)→ 通用 →「默认终端」下拉,选择 PowerShell / Git Bash / MSYS2 / WSL 之一。改动即时生效并持久化。
114
-
115
- 模型看到 `shell` 工具后,执行命令时自动使用你选择的终端(工具不暴露终端参数,模型无法更改你的选择):
116
-
117
- - 默认终端 = Git Bash 时:`shell(command: "git status")` 走 Git Bash
118
- - 默认终端 = MSYS2 时:`shell(command: "gcc --version")` 走 MSYS2(登录 shell,PATH 含 `/usr/bin` 与 `/mingw64/bin`,自带完整 GCC / mingw64 工具链)
119
- - 默认终端 = WSL 时:`shell(command: "ls -la /mnt/d/WorkSpace")` 走 WSL;传 `distro: "Ubuntu"` 可指定发行版
120
- - 默认终端 = PowerShell 时:`shell(command: "Get-Process node")` 走 PowerShell
121
-
122
- ## 配置
123
-
124
- **Web UI 设置**(推荐):设置 → 通用 →「默认终端」。
125
-
126
- 插件 row 的 `config`(覆盖默认,作为设置的 composition 基准):
127
-
128
- | 键 | 默认 | 说明 |
129
- |----|------|------|
130
- | `defaultShell` | `powershell` | 设置未覆盖时的后端 |
131
- | `timeoutMs` | 120000 | 默认超时 |
132
- | `maxTimeoutMs` | 600000 | 调用方 timeoutMs 上限 |
133
- | `pwshPath` | 自动探测 | 固定 pwsh.exe 路径 |
134
- | `gitBashPath` | 自动探测 | 固定 git bash.exe 路径 |
135
- | `msys2Path` | 自动探测 | 固定 MSYS2 入口路径(需指向 `bash.exe`,不要指向 `msys2.exe`;见「MSYS2 支持」) |
136
- | `wslPath` | 自动探测 | 固定 wsl.exe 路径 |
137
-
138
- ## 卸载
139
-
140
- ```powershell
141
- Remove-Item "$env:USERPROFILE\.dsh\profiles\web\node_modules\dsh-bash-terminal-ts" -Force
142
- # 并从 cordis.patch.yml 删掉 insert 块,重启 dsh web
143
- ```
144
-
145
- ## 沙箱(官方机制对接)
146
-
147
- `shell` 工具走 DSH 官方沙箱接缝(`ctx.sandboxPolicy` + `ctx.sandbox`):
148
-
149
- - 每次调用解析当前沙箱策略;`danger-full-access` 会话直接执行(不包装)。
150
- - PowerShell / Git Bash / MSYS2 后端经 `ctx.sandbox.confine` 包装 argv —— 与官方 executor 相同的 **fail-closed** 语义:请求受限模式但无可用后端时抛 `SandboxUnavailableError`,拒绝裸跑。
151
- - WSL 后端不包装:WSL 独立 Linux 虚拟机本身就是隔离(结果报告 `enforcement: wsl-isolation`)。
152
- - 受限模式下被沙箱拒绝时,结果携带官方标记 `[sandbox: file access denied under <mode> mode]` 与同轮升级提示;模型可凭 `sandbox_permissions` + `justification` 发起一次升级(经 `ctx.approval` 用户审批),与官方 bash/pwsh 工具完全一致。
153
- - 注意:DSH 的 Windows ACL 沙箱 launcher(`node-addon-landlock-run-win32-x64`)当前尚未在 npm 发布,本机沙箱后端暂不可用;架构已就绪,DSH 发布后自动生效。
154
-
155
- ## ⚠️ 安全说明
156
-
157
- `shell` 工具的命令**在 DSH 沙箱之外**运行,与 dsh 进程同权限(等同完整访问的命令执行),
158
- 不享受 `pwsh` 工具的 ConstrainedLanguage 限制。DSH 的文件操作工具(read/write/edit)仍受文件沙箱约束。
159
- 仅在你信任的会话中使用;需要受沙箱保护的 PowerShell 时请继续使用官方 `pwsh` 工具。
160
-
161
- ## 已知限制
162
-
163
- - 本插件仅在 `win32` 平台注册工具。
164
- - WSL 后台进程在超时/中断后可能在发行版内短暂残留(WSL 实例在最后一个进程退出后自动关闭)。
165
- - Git Bash 与 MSYS2 都是 msys2 环境,与 WSL 的 Linux 行为存在差异(路径映射、包可用性)。
166
- - 若 `C:\msys64` 装在非默认位置且不在 PATH 上,需显式配置 `msys2Path`。
167
-
168
- ## 测试
169
-
170
- ```powershell
171
- git clone https://github.com/drscrewdriver/dsh-bash-terminal-ts.git
172
- cd dsh-bash-terminal-ts
173
- npm install # 安装依赖(含 typescript)
174
- npm run build # tsc 编译 src/*.ts → lib/*.js;client.tsx → dist/client.js;test/*.ts → test-dist/
175
- npm test # node test-dist/unit.js → apply.js → client.js
176
- ```
177
-
178
- CI 在 `windows-latest` 上跑同一套(`.github/workflows/test.yml`)。
179
-
180
- ## 技术实现
181
-
182
- 运行要求:**Node.js 22+(推荐 24)**,DSH 0.1.2+。
183
-
184
- 源码为 TypeScript(`strict` + `noUncheckedIndexedAccess`),编译产物 `lib/`、`dist/` 随仓库提交,DSH 直接按 `lib/index.js` 加载,**无需安装即可使用**。
185
-
186
- require 侧依赖(`@deepseek-ai/*` 等 13 个包)全部声明为 `peerDependencies` + `peerDependenciesMeta.optional`,避免与宿主自带的副本重复安装。
187
-
188
- ## 致谢
189
-
190
- 本项目基于 [MAXeaglet/dsh-bash-terminal](https://github.com/MAXeaglet/dsh-bash-terminal) 演进 —— 原始的 `shell` 工具、PowerShell / Git Bash / WSL 三后端架构与沙箱接缝对接均来自原作者。本版在此基础上加入 MSYS2 后端、TypeScript 重写与 DSH 0.1.2 适配。
191
-
192
- ## License
193
-
194
- MIT
1
+ # dsh-bash-terminal
2
+
3
+ > 🌐 [English](README.en.md) · 社区交流:[LINUX DO](https://linux.do) · [GitHub](https://github.com/drscrewdriver/dsh-bash-terminal-ts)
4
+
5
+ ![test](https://github.com/drscrewdriver/dsh-bash-terminal-ts/actions/workflows/test.yml/badge.svg)
6
+
7
+ DSH(DeepSeek Harness)插件:一个 `shell` 工具,在 Windows 上统一执行 **PowerShell / Git Bash / MSYS2 / WSL** 四种终端命令。
8
+
9
+ > **本仓库是 `MAXeaglet/dsh-bash-terminal` 的 TypeScript 重写**,带可用的 MSYS2/MINGW64 支持。两条兼容线:
10
+ >
11
+ > | 分支 | DSH 段 | 包名 | 状态 |
12
+ > |------|--------|------|------|
13
+ > | `ts/0.1.5` | 0.1.5-alpha.1 – 0.1.5-rc.x | `dsh-bash-terminal` | 本分支;`build` / `unit` / `apply` / `client` / `terminal` 本机全绿 |
14
+ > | `main` | 0.1.2-alpha.1 – 0.1.2-rc.x | `dsh-bash-terminal-ts` | TypeScript 重写主线 |
15
+ >
16
+ > **两条线刻意使用不同包名**,因此可以并存安装、互不覆盖。请按你的 DSH 版本选分支。
17
+
18
+ | 后端 | 实际执行 | 语法 / 路径 | 环境变量 |
19
+ |------|----------|-------------|----------|
20
+ | `powershell`(默认) | `pwsh -NoLogo -NoProfile -NonInteractive -Command <cmd>` | PowerShell;`C:\...` | `$env:NAME` |
21
+ | `gitbash` | Git for Windows `bash -lc <cmd>` | POSIX;`/d/WorkSpace`;PATH 含 `/usr/bin`、`/mingw64/bin` | `$NAME` |
22
+ | `msys2` | MSYS2 `bash -lc <cmd>`(`C:\msys64\usr\bin\bash.exe`) | POSIX;`/c/...`;PATH 含 `/usr/bin`、`/mingw64/bin`(gcc / make) | `$NAME`(自动注入 `MSYSTEM=MINGW64`) |
23
+ | `wsl` | `wsl [-d <distro>] -e bash -lc <cmd>` | Linux;`/mnt/d/...` | `$NAME`(经 WSLENV) |
24
+
25
+ 每次调用都启动全新 shell:**不保留状态**(cwd / 变量 / 别名)——请传 `workdir` 而不是用 `cd`。
26
+
27
+ ## 设计要点
28
+
29
+ - **终端由用户决定,AI 无法更改**:Web UI 设置页(设置 → 通用)出现"默认终端"下拉(PowerShell / Git Bash / MSYS2 / WSL);`shell` 工具永远只使用该设置,不暴露终端参数给模型。设置通过 DSH settings 系统持久化(settings.yaml)。
30
+ - **不占用 `ctx.shell` 能力接缝**:DSH 自带的沙箱化 `pwsh` 工具保持原样可用;本插件的 `shell` 工具是**额外的**多终端入口。
31
+ - 通过共享的 `ctx.subprocess` seam 派生进程:进程树终止(Windows `taskkill /T`)、SIGTERM→grace→SIGKILL、输出 spill 文件,与官方 `dsh-tool-bash` / `dsh-tool-pwsh` 行为一致。
32
+ - 后台任务注册进通用 `jobs` registry,支持 `run_in_background` / `job_output` / `job_kill`。
33
+ - 前端设置页的「默认终端」是枚举(UI 自动渲染为下拉),模型每次调用都只按该设置执行,无法自行切换终端。
34
+
35
+ ## 安装(web profile)
36
+
37
+ ### 标准安装(npm 发布后,官方 bundle 机制)
38
+
39
+ 插件带官方 `dsh.bundle` manifest(包内 `cordis.patch.yml`),profile 列出本包时 DSH **自动应用挂载**,无需手改 profile 配置:
40
+
41
+ ```powershell
42
+ # 1. 安装插件包
43
+ npm install -g dsh-bash-terminal
44
+ dsh plugin --profile web add dsh-bash-terminal # 自动加进 profile 的 bundles 并应用 patch
45
+
46
+ # 2. patch DSH 设置白名单(DSH 限制,见下方说明)
47
+ powershell -ExecutionPolicy Bypass -File install.ps1 install
48
+
49
+ # 3. 重启 dsh web
50
+ ```
51
+
52
+ > 已用临时 profile 实测:`bundles: [dsh-bash-terminal]` → dump-config 自动出现 `tool-bash-terminal` entry。
53
+
54
+ ### 本地开发安装(junction 直连,改源码即时生效)
55
+
56
+ ```powershell
57
+ # 1. 链接插件包到 profile 的 node_modules(junction,改源码即时生效)
58
+ $profile = "$env:USERPROFILE\.dsh\profiles\web"
59
+ New-Item -ItemType Junction -Path "$profile\node_modules\dsh-bash-terminal" -Target "D:\WorkSpace\projects\dsh-bash-terminal" | Out-Null
60
+
61
+ # 2. 让插件能解析 @deepseek-ai/* 依赖(junction 到 profile 的依赖树,插件与宿主共用同一份模块实例)
62
+ New-Item -ItemType Junction -Path "D:\WorkSpace\projects\dsh-bash-terminal\node_modules\@deepseek-ai" -Target "$profile\..\node_modules\@deepseek-ai" | Out-Null
63
+
64
+ # 3. 让 profile 通过官方 bundle 挂载插件(install.ps1 install 会自动做;等价于在 dsh.profile.bundles 加 "dsh-bash-terminal")
65
+ # 4. (仅修改前端源码后)重新打包 client bundle:
66
+ # cd D:\WorkSpace\projects\dsh-bash-terminal && node scripts/build-client.mjs
67
+ # 5. 重启 dsh web
68
+ ```
69
+
70
+ > ⚠️ **不要在本项目里跑 `npm install`**:它会删掉上面第 2 步的 junction,转而给插件装一份**独立的**
71
+ > `@deepseek-ai/*` 副本 —— 插件和宿主就不再共用模块实例,宿主升级后插件会停在旧 API 上
72
+ > (本项目曾因此停在 0.1.0-rc.6)。只刷新 lock 时用 `npm install --package-lock-only`。
73
+
74
+ > **兼容性**:要求 DSH ≥ **0.1.5-rc.1**。0.1.5 把浏览器模块表里的 `@deepseek-ai/dsh-client-runtime`
75
+ > 改名为 `@deepseek-ai/dsh-client-store` 且只按精确裸名命中;旧 bundle 在新宿主上会报
76
+ > `Failed to load plugins` / `require(...) missed the module table`。
77
+ >
78
+ > 另:0.1.5 的 Web 设置面改为 `settings.describe()` 动态枚举,**不再有 namespace 白名单**
79
+ > (`settings-not-exposed` 已不存在),`install.ps1` 里的白名单 patch 只是历史遗留、可忽略。
80
+
81
+ > 当前已不再需要手动改 profile 的 `cordis.patch.yml`:插件包内自带 `dsh.bundle.patch`(包内 `cordis.patch.yml`),只要 profile 的 `dsh.profile.bundles` 里有 `dsh-bash-terminal`,DSH 就会自动挂载。
82
+
83
+ 验证组合树(无需重启):
84
+
85
+ ```powershell
86
+ node "$env:APPDATA\nvm\v24.16.0\node_modules\@deepseek-ai\dsh\lib\bin.js" --profile web --dump-config | Select-String dsh-bash-terminal
87
+ ```
88
+
89
+ ## 使用
90
+
91
+ **用户在 Web UI 设置默认终端**:打开设置(齿轮)→ 通用 →「默认终端」下拉,选择 PowerShell / Git Bash / MSYS2 / WSL 之一。改动即时生效并持久化。
92
+
93
+ 模型看到 `shell` 工具后,执行命令时自动使用你选择的终端(工具不暴露终端参数,模型无法更改你的选择):
94
+
95
+ - 默认终端 = Git Bash 时:`shell(command: "git status")` 走 Git Bash
96
+ - 默认终端 = MSYS2 时:`shell(command: "gcc --version")` 走 MSYS2(MINGW64 环境,`/mingw64/bin` 的 gcc、make 可用)
97
+ - 默认终端 = WSL 时:`shell(command: "ls -la /mnt/d/WorkSpace")` 走 WSL;传 `distro: "Ubuntu"` 可指定发行版
98
+ - 默认终端 = PowerShell 时:`shell(command: "Get-Process node")` 走 PowerShell
99
+
100
+ ## 模型使用示例
101
+
102
+ - 一次性命令(默认终端):`shell(command: "git status", description: "查看 git 状态")`
103
+ - 跨轮保持状态(交互式):`terminal(action: "open")` → 记下 `sessionId` → `terminal(action: "send", sessionId, input: "cd /d/project\n")` → `terminal(action: "send", sessionId, input: "npm run dev\n")` → `terminal(action: "close", sessionId)`
104
+ - 中断正在运行的程序:`terminal(action: "signal", sessionId, signal: "SIGINT")`
105
+ - 查看活动会话:`terminal(action: "list")`
106
+ - 沙箱拒绝后升级:`shell(command: ..., sandbox_permissions: "workspace-write", justification: "...")`
107
+
108
+ ## 配置
109
+
110
+ **Web UI 设置**(推荐):设置 → 通用 →「默认终端」。
111
+
112
+ 插件 row 的 `config`(覆盖默认,作为设置的 composition 基准):
113
+
114
+ | 键 | 默认 | 说明 |
115
+ |----|------|------|
116
+ | `defaultShell` | `powershell` | 设置未覆盖时的后端 |
117
+ | `timeoutMs` | 120000 | 默认超时 |
118
+ | `maxTimeoutMs` | 600000 | 调用方 timeoutMs 上限 |
119
+ | `pwshPath` | 自动探测 | 固定 pwsh.exe 路径 |
120
+ | `gitBashPath` | 自动探测 | 固定 git bash.exe 路径 |
121
+ | `msys2Path` | 自动探测(`C:\msys64\usr\bin\bash.exe` 优先,`msys2.exe` 兜底) | 固定 MSYS2 bash.exe 路径 |
122
+ | `wslPath` | 自动探测 | 固定 wsl.exe 路径 |
123
+
124
+ ## 发布(npm)
125
+
126
+ npm 账号已启用 2FA 发布验证,需一次性验证码:
127
+
128
+ ```powershell
129
+ cd D:\WorkSpace\projects\dsh-bash-terminal
130
+ npm publish --otp <验证码> # 验证码来自你的认证器
131
+ ```
132
+
133
+ 发布前先 `npm pack --dry-run` 检查内容、跑 `npm run build` 重建(tsc 服务端编译 + client bundle + 测试编译)。
134
+
135
+ ## 卸载
136
+
137
+ 推荐直接运行:
138
+
139
+ ```powershell
140
+ powershell -ExecutionPolicy Bypass -File install.ps1 uninstall
141
+ ```
142
+
143
+ 它会删除 junction、恢复设置白名单、清理旧版遗留的 `cordis.patch.yml` 挂载块,并从 `dsh.profile.bundles` 移除 `dsh-bash-terminal`。之后重启 dsh web 即可。
144
+
145
+ 手动卸载时,除了删除 `node_modules\dsh-bash-terminal`,还要记得从 profile `package.json` 的 `dsh.profile.bundles` 中移除 `dsh-bash-terminal`。
146
+
147
+ ## 交互式终端(terminal 工具)
148
+
149
+ `terminal` 工具在 PTY 接缝(node-pty;Windows 上因上游 `spawnTerminal` 的 process inspector 仅支持 POSIX,由 `lib/terminal.js` 直连 node-pty,非 Windows 仍走官方 `ctx.subprocess.spawnTerminal`)上提供**持久交互会话**:
150
+
151
+ - `action: open` 启动一个真实终端会话(按你设置的默认终端;wsl 可传 `distro`),返回 `sessionId`
152
+ - `action: send` 写入输入并读新输出;`action: read` 只读不写;`action: signal` 向前台进程组发信号(SIGINT = Ctrl+C)
153
+ - `action: close` 终止会话
154
+ - **会话状态跨调用保持**(cwd / 变量 / 别名),适合 REPL、ssh、交互式 CLI
155
+ - `send` 会等待输出稳定(300ms 静默,上限 5s)返回**完整回复**;输出超 1MB 时报 `truncated` 提示
156
+ - 输入用 `\\n`(或 \\r)结尾表示回车
157
+
158
+ ## 沙箱(官方机制对接)
159
+
160
+ `shell` 工具走 DSH 官方沙箱接缝(`ctx.sandboxPolicy` + `ctx.sandbox`):
161
+
162
+ - 每次调用解析当前沙箱策略;`danger-full-access` 会话直接执行(不包装)。
163
+ - PowerShell 后端经 `ctx.sandbox.confine` 包装 argv —— 与官方 executor 相同的 **fail-closed** 语义:请求受限模式但无可用后端时抛 `SandboxUnavailableError`,拒绝裸跑。
164
+ - Git Bash 后端不包装:DSH 的 Windows ACL 受限令牌 runner 与 Cygwin/MSYS2 不兼容(bash 启动即因 `CreateFileMapping` Win32 error 5 终止),因此 Git Bash 在受限模式下也不经沙箱包装;结果报告 `enforcement: gitbash-unconfined`。
165
+ - MSYS2 后端同样不包装(同一 Cygwin/MSYS2 运行时不兼容);结果报告 `enforcement: msys2-unconfined`。
166
+ - WSL 后端不包装:WSL 独立 Linux 虚拟机本身就是隔离(结果报告 `enforcement: wsl-isolation`)。
167
+ - 受限模式下被沙箱拒绝时,结果携带官方标记 `[sandbox: file access denied under <mode> mode]` 与同轮升级提示;模型可凭 `sandbox_permissions` + `justification` 发起一次升级(经 `ctx.approval` 用户审批),与官方 bash/pwsh 工具完全一致。
168
+ - 注意:DSH 的 Windows ACL runner 可用时,PowerShell 的受限模式会经它包装;Git Bash 与 MSYS2 因 Cygwin/MSYS2 不兼容而保持不包装。
169
+
170
+ ## ⚠️ 安全说明
171
+
172
+ `shell` 工具在受限模式下:PowerShell 会经 `ctx.sandbox.confine` 包装(fail-closed);Git Bash 与 MSYS2 因 Cygwin/MSYS2 与 Windows ACL 受限令牌不兼容而**不包装**(与 dsh 进程同权限);WSL 因独立 Linux VM 不包装。它是**额外的多终端入口**,不享受官方 `pwsh` 工具的 ConstrainedLanguage 限制。DSH 的文件操作工具(read/write/edit)仍受文件沙箱约束。仅在你信任的会话中使用;需要受沙箱保护的 PowerShell 时请继续使用官方 `pwsh` 工具。
173
+
174
+ ## 交互终端已知限制(ConPTY)
175
+
176
+ - **PowerShell 5.1 无法在 ConPTY 启动**(0x8009001d)—— 交互式 PowerShell 需要安装 [PowerShell 7](https://github.com/PowerShell/PowerShell/releases)(一次性命令不受影响)。
177
+ - **wsl.exe 交互模式在 ConPTY 下可能触发 WSL 服务 RPC 错误**(0x8007072c,偶发)—— 一次性 `wsl -e bash -lc ...` 命令正常;交互会话建议直接用 Windows Terminal / WSL 终端,或重试。
178
+ - **Windows 上 node-pty 不接受命名信号**:`signal` 的 `SIGINT` 映射为 Ctrl+C(`\x03`),其他信号(`SIGTERM` / `SIGKILL` / `SIGTSTP` / `SIGHUP`)退化为终止会话。
179
+ - Git Bash 交互会话完全正常。
180
+
181
+ ## 已知限制
182
+
183
+ - WSL 后台进程在超时/中断后可能在发行版内短暂残留(WSL 实例在最后一个进程退出后自动关闭)。
184
+ - Git Bash 是 msys2 环境,与 WSL 的 Linux 行为存在差异(路径映射、包可用性)。
185
+ - MSYS2 后端需要本机安装 MSYS2(默认 `C:\msys64`);未安装时 `shell` 报 `backend unavailable`,可用 `msys2Path` 指定自定义位置。候选顺序永远是 `bash.exe` 优先、`msys2.exe` 兜底——`msys2.exe` 在管道 stdio 下会静默返回零字节,仅作最后手段。
186
+ - 本插件仅在 `win32` 平台注册工具。
187
+
188
+ ## 测试
189
+
190
+ ```powershell
191
+ cd D:\WorkSpace\projects\dsh-bash-terminal
192
+ npm install # 安装依赖(含 typescript)
193
+ npm run build # tsc 编译 src/*.ts → lib/*.js;client.tsx → lib/client.js + dist/client.js;test/*.ts → test-dist/
194
+ npm test # node test-dist/unit.js → apply.js → client.js → terminal.js
195
+ ```
196
+
197
+ 源码为 TypeScript(`strict` + `noUncheckedIndexedAccess`),编译产物 `lib/`、`dist/` 随仓库提交,DSH 直接按 `lib/index.js` 加载,无需安装即可使用。
package/cordis.patch.yml CHANGED
@@ -1,5 +1,5 @@
1
- # dsh-bash-terminal-ts bundle layer — applied automatically when a profile lists
2
- # this package in dsh.profile.bundles (dsh.bundle.patch declaration).
3
- - insert:
4
- - id: tool-bash-terminal
5
- name: 'dsh-bash-terminal-ts'
1
+ # dsh-bash-terminal bundle layer — applied automatically when a profile lists
2
+ # this package in dsh.profile.bundles (dsh.bundle.patch declaration).
3
+ - insert:
4
+ - id: tool-bash-terminal
5
+ name: 'dsh-bash-terminal'
@@ -23,13 +23,24 @@ __export(client_exports, {
23
23
  inject: () => inject
24
24
  });
25
25
  module.exports = __toCommonJS(client_exports);
26
+ var import_react = require("react");
26
27
  var import_dsh_client_store = require("@deepseek-ai/dsh-client-store");
28
+ var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
27
29
  var import_jsx_runtime = require("react/jsx-runtime");
28
30
  var SETTINGS_NS = "settings.bash-terminal";
29
31
  var SETTINGS_NAMESPACE = "bash-terminal";
32
+ var SHELLS = ["powershell", "gitbash", "msys2", "wsl"];
33
+ var ROW_CSS = ".btRow{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:center;gap:8px;padding:16px 0;display:flex}.btRowText{flex-direction:column;flex:1;gap:4px;min-width:0;padding-right:48px;display:flex}.btTitle{color:var(--dsw-alias-label-primary);font-size:14px;font-weight:400;line-height:22px}.btDesc{color:var(--dsw-alias-label-tertiary);font-size:12px;font-weight:400;line-height:18px}.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}.btSelector:hover{background:var(--dsw-alias-interactive-bg-hover)}.btChevron{flex:none}";
34
+ if (typeof document !== "undefined" && document.querySelector('style[data-plugin-css="bash-terminal-row"]') === null) {
35
+ const tag = document.createElement("style");
36
+ tag.dataset.plugin = "dsh-bash-terminal";
37
+ tag.dataset.pluginCss = "bash-terminal-row";
38
+ tag.textContent = ROW_CSS;
39
+ document.head.appendChild(tag);
40
+ }
30
41
  var zh = {
31
42
  "shell.title": "\u9ED8\u8BA4\u7EC8\u7AEF",
32
- "shell.description": "shell \u5DE5\u5177\u6267\u884C\u547D\u4EE4\u65F6\u4F7F\u7528\u7684\u7EC8\u7AEF\uFF08\u7531\u4F60\u51B3\u5B9A\uFF0CAI \u65E0\u6CD5\u66F4\u6539\uFF09",
43
+ "shell.description": "shell \u5DE5\u5177\u6267\u884C\u547D\u4EE4\u65F6\u4F7F\u7528\u7684\u7EC8\u7AEF",
33
44
  "shell.powershell": "PowerShell",
34
45
  "shell.gitbash": "Git Bash",
35
46
  "shell.msys2": "MSYS2",
@@ -37,7 +48,7 @@ var zh = {
37
48
  };
38
49
  var en = {
39
50
  "shell.title": "Default terminal",
40
- "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",
41
52
  "shell.powershell": "PowerShell",
42
53
  "shell.gitbash": "Git Bash",
43
54
  "shell.msys2": "MSYS2",
@@ -47,49 +58,45 @@ var inject = ["slots", "locale", "settingsScope"];
47
58
  function ShellPreferenceRow({ t, useStore, setShell }) {
48
59
  const shell = useStore((s) => s.shell);
49
60
  const writable = useStore((s) => s.writable);
50
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
51
- "div",
52
- {
53
- style: {
54
- display: "flex",
55
- alignItems: "center",
56
- justifyContent: "space-between",
57
- gap: 16,
58
- padding: "12px 0"
59
- },
60
- children: [
61
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: { minWidth: 0 }, children: [
62
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 14, fontWeight: 500, lineHeight: "22px", color: "var(--dsw-alias-label-primary, inherit)" }, children: t("shell.title") }),
63
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { fontSize: 12, lineHeight: "18px", opacity: 0.65 }, children: t("shell.description") })
64
- ] }),
65
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
66
- "select",
61
+ const [open, setOpen] = (0, import_react.useState)(false);
62
+ const items = SHELLS.map((id) => ({ id, label: t("shell." + id) }));
63
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "btRow", children: [
64
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "btRowText", children: [
65
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "btTitle", children: t("shell.title") }),
66
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "btDesc", children: t("shell.description") })
67
+ ] }),
68
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
69
+ import_dsh_client_ui_primitives.Menu,
70
+ {
71
+ open,
72
+ onClose: () => setOpen(false),
73
+ items,
74
+ selectedId: shell,
75
+ onSelect: (id) => {
76
+ setOpen(false);
77
+ setShell(id);
78
+ },
79
+ align: "end",
80
+ portal: true,
81
+ anchor: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
82
+ "button",
67
83
  {
68
- value: shell,
84
+ type: "button",
85
+ className: "btSelector",
86
+ "aria-haspopup": "menu",
87
+ "aria-expanded": open,
69
88
  disabled: !writable,
70
- onChange: (e) => setShell(e.target.value),
71
- style: {
72
- fontSize: 14,
73
- padding: "6px 10px",
74
- borderRadius: 8,
75
- border: "1px solid var(--dsw-alias-line-strong, #ccc)",
76
- background: "var(--dsw-alias-bg-layer-2, #fff)",
77
- color: "var(--dsw-alias-label-primary, inherit)",
78
- outline: "none",
79
- cursor: writable ? "pointer" : "not-allowed",
80
- maxWidth: 180
81
- },
89
+ onClick: () => setOpen(!open),
90
+ style: !writable ? { opacity: 0.5, cursor: "not-allowed" } : void 0,
82
91
  children: [
83
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "powershell", children: t("shell.powershell") }),
84
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "gitbash", children: t("shell.gitbash") }),
85
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "msys2", children: t("shell.msys2") }),
86
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "wsl", children: t("shell.wsl") })
92
+ t("shell." + shell),
93
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_dsh_client_ui_primitives.IconChevronDownOutline14, { className: "btChevron" })
87
94
  ]
88
95
  }
89
96
  )
90
- ]
91
- }
92
- );
97
+ }
98
+ )
99
+ ] });
93
100
  }
94
101
  function apply(ctx) {
95
102
  ctx.effect(() => ctx.locale.register(SETTINGS_NS, { zh, en }), "bash-terminal: settings dictionaries");