dsh-plugin-pwsh7 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +185 -2
- package/README.zh.md +176 -0
- package/cordis.patch.yml +16 -0
- package/icon.svg +28 -0
- package/index.js +126 -0
- package/lib/executors/pwsh.mjs +20 -0
- package/lib/install.mjs +978 -0
- package/lib/paths.mjs +327 -0
- package/lib/shipped.mjs +115 -0
- package/lib/state.mjs +129 -0
- package/lib/status.mjs +187 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +58 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 dsh-plugin-pwsh7 contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,186 @@
|
|
|
1
|
-
#
|
|
1
|
+
# dsh-plugin-pwsh7
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/dsh-plugin-pwsh7)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
Windows DeepSeek Harness (DSH) bundle that routes the model-facing **`pwsh` tool to PowerShell 7**
|
|
7
|
+
(`pwsh.exe`, major version >= 7) instead of falling back to Windows PowerShell 5.1.
|
|
8
|
+
|
|
9
|
+
- The executor inherits every behavior of the stock `dsh-pwsh-sandbox` executor: sandbox modes,
|
|
10
|
+
`sandbox_permissions` escalation, denial/runner-failure facts, background jobs, output caps, and
|
|
11
|
+
exit markers. It only replaces executable resolution.
|
|
12
|
+
- When PowerShell 7 is missing, a model-facing **`shell_router`** tool reports status, diagnoses
|
|
13
|
+
sandbox problems, and installs PowerShell 7 per-user (no administrator required).
|
|
14
|
+
|
|
15
|
+
English | [中文说明](README.zh.md)
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Why
|
|
20
|
+
|
|
21
|
+
On Windows, DSH mounts `pwsh-sandbox` as `ctx.shell`, but the resolved executable can end up being
|
|
22
|
+
Windows PowerShell 5.1 in some environments. This bundle keeps the stock PowerShell executor and
|
|
23
|
+
overrides only its `pwshPath` resolution:
|
|
24
|
+
|
|
25
|
+
1. **Replace the `pwsh-sandbox` row** with `RouterPwshExecutor extends SandboxPwshExecutor`. An
|
|
26
|
+
explicit `config.pwshPath` still wins; otherwise the plugin-resolved PowerShell 7 is used.
|
|
27
|
+
2. **Register the `shell_router` tool** (`status` / `diagnose` / `install`) plus a small system-prompt
|
|
28
|
+
section describing the routing.
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
- Windows 10/11.
|
|
33
|
+
- A DSH profile that supports bundles (`@deepseek-ai/dsh-base` / Web profile). Tested against
|
|
34
|
+
DSH desktop 0.2.1-rc.2.
|
|
35
|
+
- Node.js >= 22 is only needed to run the repository test suite.
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
Install from npm through the DSH plugin manager:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
plugin_manager action=install_bundle target=dsh-plugin-pwsh7@0.1.0
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or install from a local checkout (a directory install links the checkout, which is convenient while
|
|
46
|
+
developing):
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
plugin_manager action=install_bundle target=D:\path\to\dsh-plugin-pwsh7
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The new bundle is applied through HMR immediately. After changing the plugin's source, restart DSH
|
|
53
|
+
to load a fresh module generation.
|
|
54
|
+
|
|
55
|
+
## Usage
|
|
56
|
+
|
|
57
|
+
The model-facing `shell_router` tool:
|
|
58
|
+
|
|
59
|
+
| Action | What it does |
|
|
60
|
+
|---|---|
|
|
61
|
+
| `status` (default) | Resolved PowerShell 7 path and version, config file, current sandbox policy, shipped-package resolution. |
|
|
62
|
+
| `diagnose` | Runs real PowerShell 7 commands under the **current sandbox policy** (version + workspace write) and reports exit codes, stdout/stderr, denial/runner-failure classification, and concrete hints. |
|
|
63
|
+
| `install` | Ensures PowerShell 7 exists. `method: "auto"` (default) prefers the per-user portable zip, then falls back to winget; `portable`/`winget` force one strategy. Existing working installations are reused instead of downloading again. |
|
|
64
|
+
|
|
65
|
+
### Explicit paths
|
|
66
|
+
|
|
67
|
+
Resolution order for the pwsh executable:
|
|
68
|
+
|
|
69
|
+
1. `DSH_PWSH_PATH` environment variable
|
|
70
|
+
2. `pwshPath` in `%LOCALAPPDATA%\dsh\pwsh7\config.json`
|
|
71
|
+
3. `%ProgramFiles%\PowerShell\7\pwsh.exe`
|
|
72
|
+
4. `%LOCALAPPDATA%\dsh\pwsh7\powershell7\…\pwsh.exe` (portable installs, newest first)
|
|
73
|
+
5. `%LOCALAPPDATA%\Programs\PowerShell\7\pwsh.exe`
|
|
74
|
+
6. every `PATH` entry
|
|
75
|
+
7. `%LOCALAPPDATA%\Microsoft\WindowsApps\pwsh.exe` (Store alias)
|
|
76
|
+
8. `%ProgramFiles(x86)%\PowerShell\7\pwsh.exe`
|
|
77
|
+
|
|
78
|
+
The stock `pwsh-sandbox` row still accepts its official `pwshPath` config field; an explicit config
|
|
79
|
+
value takes precedence over automatic detection.
|
|
80
|
+
|
|
81
|
+
### Config file
|
|
82
|
+
|
|
83
|
+
`%LOCALAPPDATA%\dsh\pwsh7\config.json`:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"version": 1,
|
|
88
|
+
"pwshPath": "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
|
|
89
|
+
"installed": {
|
|
90
|
+
"pwsh": {
|
|
91
|
+
"path": "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
|
|
92
|
+
"version": "7.6.7",
|
|
93
|
+
"method": "existing",
|
|
94
|
+
"at": "2026-10-17T00:00:00.000Z"
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
"updatedAt": "2026-10-17T00:00:00.000Z"
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
## Automatic installation
|
|
102
|
+
|
|
103
|
+
Per-user, no administrator rights:
|
|
104
|
+
|
|
105
|
+
| Component | Source | Destination |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| PowerShell 7 | Latest `PowerShell/PowerShell` GitHub release, `PowerShell-*-win-x64.zip` | `%LOCALAPPDATA%\dsh\pwsh7\powershell7\<version>\` |
|
|
108
|
+
|
|
109
|
+
- HTTPS streaming download into `%LOCALAPPDATA%\dsh\pwsh7\tmp`, with progress callbacks and
|
|
110
|
+
cancellation.
|
|
111
|
+
- Extraction uses the Windows built-in `tar.exe` (bsdtar). No downloaded installer is ever executed.
|
|
112
|
+
- After extraction the new `pwsh.exe` is executed once to verify a major version >= 7 before it is
|
|
113
|
+
written into `config.json`.
|
|
114
|
+
- `auto` falls back to `winget install Microsoft.PowerShell`; when elevation would be required the
|
|
115
|
+
result carries the exact manual elevated command instead of hanging on UAC.
|
|
116
|
+
- No writes outside the router root and its temp directory.
|
|
117
|
+
- Live-tested on Windows: 106,364,316-byte zip downloaded in ~31 s, extracted to 244.8 MB, verified
|
|
118
|
+
`7.6.7`.
|
|
119
|
+
|
|
120
|
+
## Sandbox behavior
|
|
121
|
+
|
|
122
|
+
- The executor runs through `ctx.sandbox.confine()` and the Windows ACL restricted-token runner, with
|
|
123
|
+
the same `workspace-write` / `read-only` / `danger-full-access` semantics as the stock executor.
|
|
124
|
+
- PowerShell 7 works in all three modes (verified: `workspace-write` can write inside the workspace
|
|
125
|
+
and is denied outside it; `danger-full-access` is unconfined).
|
|
126
|
+
- If the Windows sandbox reports `SetNamedSecurityInfoW failed (Win32 5)` / `grantWrite(<workspace>)`,
|
|
127
|
+
the workspace DACL is missing rights. Run DSH's bundled **`diagnose-windows-sandbox-acl`** skill
|
|
128
|
+
against the workspace root (it backs up and verifies), or temporarily switch the session to full
|
|
129
|
+
access.
|
|
130
|
+
|
|
131
|
+
## Troubleshooting
|
|
132
|
+
|
|
133
|
+
1. Run `shell_router action="status"` and check the resolved path/version.
|
|
134
|
+
2. Run `shell_router action="diagnose"` to execute real commands under the current sandbox policy.
|
|
135
|
+
3. Common causes:
|
|
136
|
+
- **Still Windows PowerShell 5.1** — detection did not find PowerShell 7. Set `DSH_PWSH_PATH` or
|
|
137
|
+
the `pwshPath` config entry, then check `status` again.
|
|
138
|
+
- **PowerShell 7 installed in a custom location** — set `DSH_PWSH_PATH` or `config.json`.
|
|
139
|
+
- **Sandbox/ACL failure** — see the sandbox section above.
|
|
140
|
+
|
|
141
|
+
## Architecture
|
|
142
|
+
|
|
143
|
+
| Path | Role |
|
|
144
|
+
|---|---|
|
|
145
|
+
| `index.js` | Plugin entry: `shell_router` tool, system-prompt section, state refresh on config reload |
|
|
146
|
+
| `lib/shipped.mjs` | Loads the shipped `@deepseek-ai/*` packages from the running DSH installation (`app.asar`) |
|
|
147
|
+
| `lib/paths.mjs` | PowerShell 7 detection, version probing, `config.json` IO |
|
|
148
|
+
| `lib/install.mjs` | Per-user portable install (official zip) + winget fallback |
|
|
149
|
+
| `lib/state.mjs` | Process-wide resolution cache shared by the executor and the tool |
|
|
150
|
+
| `lib/status.mjs` | `status` / `diagnose` implementations |
|
|
151
|
+
| `lib/executors/pwsh.mjs` | PowerShell 7 executor (extends the official `SandboxPwshExecutor`) |
|
|
152
|
+
| `cordis.patch.yml` | Bundle patch: replaces `pwsh-sandbox` and inserts the plugin row (Windows only) |
|
|
153
|
+
| `locale/en.json`, `locale/zh.json`, `icon.svg` | Plugin-manager display metadata |
|
|
154
|
+
|
|
155
|
+
`package.json` `files` ships only the runtime pieces; `test/` stays in the repository.
|
|
156
|
+
|
|
157
|
+
## Development
|
|
158
|
+
|
|
159
|
+
```powershell
|
|
160
|
+
# Plain Node tests (no running DSH required)
|
|
161
|
+
node --test test/paths.test.mjs test/install.test.mjs
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
- 49 tests: 48 pass, 0 fail, 1 skipped (network-gated by default).
|
|
165
|
+
- `DSH_PWSH7_NETWORK_TESTS=1` enables the live GitHub release / HEAD checks: 30/30 pass.
|
|
166
|
+
- The environment override prefix is `DSH_PWSH7_*` (`APP_ROOT`, `TAR`, `NETWORK_TESTS`).
|
|
167
|
+
|
|
168
|
+
## Versioning note
|
|
169
|
+
|
|
170
|
+
`0.1.0` is the first public release. Earlier internal iterations (0.2.x/0.3.x) included MSYS2 bash
|
|
171
|
+
routing and used a different package name; that scope was removed because MSYS2/Cygwin cannot start
|
|
172
|
+
under the DSH Windows ACL sandbox. The historical evidence stays in the build workspace
|
|
173
|
+
(`sandbox-recon/FINDINGS.md`), not in this package.
|
|
174
|
+
|
|
175
|
+
## Uninstall
|
|
176
|
+
|
|
177
|
+
```
|
|
178
|
+
plugin_manager action=remove_bundle target=dsh-plugin-pwsh7
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
The native Windows preset is restored and `pwsh` returns to DSH's default resolution. The per-user
|
|
182
|
+
directory `%LOCALAPPDATA%\dsh\pwsh7\` is not deleted automatically.
|
|
183
|
+
|
|
184
|
+
## License
|
|
185
|
+
|
|
186
|
+
MIT — see [LICENSE](LICENSE).
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
# dsh-plugin-pwsh7
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/dsh-plugin-pwsh7)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
Windows 上的 DeepSeek Harness(DSH)插件:把模型侧的 **`pwsh` 工具路由到 PowerShell 7**
|
|
7
|
+
(`pwsh.exe`,主版本 >= 7),不再落到 Windows PowerShell 5.1。
|
|
8
|
+
|
|
9
|
+
- 执行器继承 DSH 自带 `dsh-pwsh-sandbox` 的全部行为:沙箱模式、`sandbox_permissions` 升权、
|
|
10
|
+
拒绝/runner 失败标记、后台任务、输出上限与 exit marker;本插件只替换可执行文件解析。
|
|
11
|
+
- 找不到 PowerShell 7 时,提供模型可调用的 **`shell_router`** 工具做检测、诊断和
|
|
12
|
+
**用户级自动下载安装**(默认不需要管理员权限)。
|
|
13
|
+
|
|
14
|
+
[English](README.md) | 中文
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 它做什么
|
|
19
|
+
|
|
20
|
+
Windows 预设把 `pwsh-sandbox` 行挂到 `ctx.shell`,但可执行文件解析在部分环境里会落到
|
|
21
|
+
Windows PowerShell 5.1。本插件保留官方执行器,只覆盖 `pwshPath` 解析:
|
|
22
|
+
|
|
23
|
+
1. **替换 `pwsh-sandbox` 行**:`RouterPwshExecutor extends SandboxPwshExecutor`。显式
|
|
24
|
+
`config.pwshPath` 优先;否则使用插件检测到的 PowerShell 7。
|
|
25
|
+
2. **注册 `shell_router` 工具**(`status` / `diagnose` / `install`),并附一段描述路由与安装方式的
|
|
26
|
+
系统提示小节。
|
|
27
|
+
|
|
28
|
+
## 环境要求
|
|
29
|
+
|
|
30
|
+
- Windows 10/11。
|
|
31
|
+
- 支持 bundle 的 DSH profile(`@deepseek-ai/dsh-base` / Web profile)。已在 DSH desktop
|
|
32
|
+
0.2.1-rc.2 上验证。
|
|
33
|
+
- Node.js >= 22 仅运行仓库测试时需要。
|
|
34
|
+
|
|
35
|
+
## 安装
|
|
36
|
+
|
|
37
|
+
通过 DSH 插件管理器从 npm 安装:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
plugin_manager action=install_bundle target=dsh-plugin-pwsh7@0.1.0
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
或从本地目录安装(目录安装会链接源码,便于开发):
|
|
44
|
+
|
|
45
|
+
```
|
|
46
|
+
plugin_manager action=install_bundle target=D:\path\to\dsh-plugin-pwsh7
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
新 bundle 通过 HMR 立即生效;之后修改插件源码需要重启 DSH 才会加载新模块。
|
|
50
|
+
|
|
51
|
+
## 使用
|
|
52
|
+
|
|
53
|
+
模型工具 `shell_router`:
|
|
54
|
+
|
|
55
|
+
| 动作 | 说明 |
|
|
56
|
+
|---|---|
|
|
57
|
+
| `status`(默认) | 报告解析到的 PowerShell 7 路径与版本、配置文件、当前沙箱策略、`@deepseek-ai/*` 加载状态。 |
|
|
58
|
+
| `diagnose` | 在**当前沙箱策略**下真实运行 PowerShell 7 命令(版本 + 工作区写入),输出退出码、stdout/stderr、拒绝/runner 失败判定与具体修复建议。 |
|
|
59
|
+
| `install` | 确保 PowerShell 7 可用。`method: "auto"`(默认)优先用户级便携 zip,失败回退 winget;`portable`/`winget` 强制单一方式。已有可用安装时只刷新配置,不会重复下载。 |
|
|
60
|
+
|
|
61
|
+
### 显式指定路径
|
|
62
|
+
|
|
63
|
+
`pwsh` 可执行文件解析顺序:
|
|
64
|
+
|
|
65
|
+
1. 环境变量 `DSH_PWSH_PATH`
|
|
66
|
+
2. `%LOCALAPPDATA%\dsh\pwsh7\config.json` 的 `pwshPath`
|
|
67
|
+
3. `%ProgramFiles%\PowerShell\7\pwsh.exe`
|
|
68
|
+
4. `%LOCALAPPDATA%\dsh\pwsh7\powershell7\…\pwsh.exe`(便携安装,新版本优先)
|
|
69
|
+
5. `%LOCALAPPDATA%\Programs\PowerShell\7\pwsh.exe`
|
|
70
|
+
6. `PATH` 各目录
|
|
71
|
+
7. `%LOCALAPPDATA%\Microsoft\WindowsApps\pwsh.exe`(Store 别名)
|
|
72
|
+
8. `%ProgramFiles(x86)%\PowerShell\7\pwsh.exe`
|
|
73
|
+
|
|
74
|
+
官方 `pwsh-sandbox` 行本身仍支持 `pwshPath` 配置字段;显式配置优先于自动检测。
|
|
75
|
+
|
|
76
|
+
### 配置文件
|
|
77
|
+
|
|
78
|
+
`%LOCALAPPDATA%\dsh\pwsh7\config.json`:
|
|
79
|
+
|
|
80
|
+
```json
|
|
81
|
+
{
|
|
82
|
+
"version": 1,
|
|
83
|
+
"pwshPath": "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
|
|
84
|
+
"installed": {
|
|
85
|
+
"pwsh": {
|
|
86
|
+
"path": "C:\\Program Files\\PowerShell\\7\\pwsh.exe",
|
|
87
|
+
"version": "7.6.7",
|
|
88
|
+
"method": "existing",
|
|
89
|
+
"at": "2026-10-17T00:00:00.000Z"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"updatedAt": "2026-10-17T00:00:00.000Z"
|
|
93
|
+
}
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## 自动安装
|
|
97
|
+
|
|
98
|
+
默认用户级、无管理员权限:
|
|
99
|
+
|
|
100
|
+
| 组件 | 来源 | 安装位置 |
|
|
101
|
+
|---|---|---|
|
|
102
|
+
| PowerShell 7 | GitHub `PowerShell/PowerShell` 最新 release 的 `PowerShell-*-win-x64.zip` | `%LOCALAPPDATA%\dsh\pwsh7\powershell7\<version>\` |
|
|
103
|
+
|
|
104
|
+
- HTTPS 流式下载到 `%LOCALAPPDATA%\dsh\pwsh7\tmp`,支持进度回调与取消。
|
|
105
|
+
- 解包使用 Windows 自带 `tar.exe`(bsdtar);不会执行任何下载来的安装器。
|
|
106
|
+
- 解包后先真实运行一次新 `pwsh.exe` 验证主版本 >= 7,再写入 `config.json`。
|
|
107
|
+
- `auto` 失败回退 `winget install Microsoft.PowerShell`;需要 UAC 提升时返回明确的手动命令,
|
|
108
|
+
不会卡在提权提示上。
|
|
109
|
+
- 不写入安装目录与临时目录以外的位置。
|
|
110
|
+
- 已在本机实测:下载 106,364,316 字节(约 31s),解压后 244.8 MB,验证为 `7.6.7`。
|
|
111
|
+
|
|
112
|
+
## 沙箱
|
|
113
|
+
|
|
114
|
+
- 执行器通过 `ctx.sandbox.confine()` 走 Windows ACL restricted-token runner,
|
|
115
|
+
`workspace-write` / `read-only` / `danger-full-access` 与官方执行器一致。
|
|
116
|
+
- PowerShell 7 在三种模式下都可用(已实测:`workspace-write` 下工作区内可写、工作区外写入被拒;
|
|
117
|
+
`danger-full-access` 不受限)。
|
|
118
|
+
- 若出现 `SetNamedSecurityInfoW failed (Win32 5)` / `grantWrite(<workspace>)`,说明工作区 DACL
|
|
119
|
+
缺少权限。可运行 DSH 内置的 **`diagnose-windows-sandbox-acl`** skill 对工作区根目录做诊断+修复
|
|
120
|
+
(先备份、再校验),或临时把会话切到完全权限。
|
|
121
|
+
|
|
122
|
+
## 故障排查
|
|
123
|
+
|
|
124
|
+
1. 先跑 `shell_router action="status"`:确认 PowerShell 7 的解析路径与版本。
|
|
125
|
+
2. 再跑 `shell_router action="diagnose"`:在当前沙箱模式下真实执行命令并分类失败。
|
|
126
|
+
3. 常见原因:
|
|
127
|
+
- **仍然是 Windows PowerShell 5.1**:检测没找到 PowerShell 7。设置 `DSH_PWSH_PATH` 或
|
|
128
|
+
`config.json` 的 `pwshPath`,再查看 `status`。
|
|
129
|
+
- **PowerShell 7 装在自定义位置**:设置 `DSH_PWSH_PATH` 或 `config.json`。
|
|
130
|
+
- **沙箱 / ACL 失败**:见上一节。
|
|
131
|
+
|
|
132
|
+
## 结构
|
|
133
|
+
|
|
134
|
+
| 路径 | 作用 |
|
|
135
|
+
|---|---|
|
|
136
|
+
| `index.js` | 插件入口:`shell_router` 工具、系统提示小节、配置热重载时刷新检测 |
|
|
137
|
+
| `lib/shipped.mjs` | 从运行中的 DSH 安装(Electron `app.asar`)加载 `@deepseek-ai/*` 包 |
|
|
138
|
+
| `lib/paths.mjs` | PowerShell 7 路径探测、版本探测、`config.json` 读写 |
|
|
139
|
+
| `lib/install.mjs` | 用户级便携安装(官方 zip)+ winget 回退 |
|
|
140
|
+
| `lib/state.mjs` | 进程内解析缓存(executor 与工具共用) |
|
|
141
|
+
| `lib/status.mjs` | `status` / `diagnose` 实现 |
|
|
142
|
+
| `lib/executors/pwsh.mjs` | PowerShell 7 执行器(继承官方 `SandboxPwshExecutor`) |
|
|
143
|
+
| `cordis.patch.yml` | bundle patch:Windows 上替换 `pwsh-sandbox` 并插入插件行 |
|
|
144
|
+
| `locale/en.json`, `locale/zh.json`, `icon.svg` | 插件管理页显示元数据 |
|
|
145
|
+
|
|
146
|
+
`package.json` 的 `files` 只打包运行所需文件;`test/` 保留在仓库中,不进入 npm 包。
|
|
147
|
+
|
|
148
|
+
## 开发
|
|
149
|
+
|
|
150
|
+
```powershell
|
|
151
|
+
# 纯 Node 单元测试(不需要 DSH 运行)
|
|
152
|
+
node --test test/paths.test.mjs test/install.test.mjs
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
- 49 tests:48 pass,0 fail,1 skipped(网络用例默认跳过)。
|
|
156
|
+
- 设置 `DSH_PWSH7_NETWORK_TESTS=1` 会启用真实 GitHub release / HEAD 校验:30/30 pass。
|
|
157
|
+
- 环境变量覆盖前缀为 `DSH_PWSH7_*`(`APP_ROOT`、`TAR`、`NETWORK_TESTS`)。
|
|
158
|
+
|
|
159
|
+
## 版本说明
|
|
160
|
+
|
|
161
|
+
`0.1.0` 是第一个公开发布版本。更早的内部迭代(0.2.x/0.3.x)曾包含 MSYS2 bash 路由并使用过
|
|
162
|
+
不同的包名;由于 MSYS2/Cygwin 无法在 DSH Windows ACL 沙箱中启动,该部分已被移除。历史实测证据
|
|
163
|
+
保留在构建工作区的 `sandbox-recon/FINDINGS.md`,不在本包内。
|
|
164
|
+
|
|
165
|
+
## 卸载
|
|
166
|
+
|
|
167
|
+
```
|
|
168
|
+
plugin_manager action=remove_bundle target=dsh-plugin-pwsh7
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
卸载后原生 Windows 预设恢复,`pwsh` 回到 DSH 原有解析。用户级目录
|
|
172
|
+
`%LOCALAPPDATA%\dsh\pwsh7\` 不会自动删除,可按需手动清理。
|
|
173
|
+
|
|
174
|
+
## License
|
|
175
|
+
|
|
176
|
+
MIT,见 [LICENSE](LICENSE)。
|
package/cordis.patch.yml
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# dsh-plugin-pwsh7 — bundle patch.
|
|
2
|
+
#
|
|
3
|
+
# Windows only:
|
|
4
|
+
# Replace the host pwsh executor with one that resolves PowerShell 7 explicitly, keeping the
|
|
5
|
+
# stock sandbox executor behavior (escalation, denial facts, jobs, output caps).
|
|
6
|
+
#
|
|
7
|
+
# On other platforms the rows are disabled: the stock POSIX composition stays untouched.
|
|
8
|
+
|
|
9
|
+
- id: pwsh-sandbox
|
|
10
|
+
name: 'dsh-plugin-pwsh7/pwsh-executor'
|
|
11
|
+
disabled: !!js process.platform !== 'win32'
|
|
12
|
+
|
|
13
|
+
- insert:
|
|
14
|
+
- id: pwsh7-router
|
|
15
|
+
name: 'dsh-plugin-pwsh7'
|
|
16
|
+
disabled: !!js process.platform !== 'win32'
|
package/icon.svg
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="128" height="128" viewBox="0 0 128 128" role="img" aria-label="PowerShell 7 Router">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="bg" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#071b30"/>
|
|
5
|
+
<stop offset="1" stop-color="#0f4f72"/>
|
|
6
|
+
</linearGradient>
|
|
7
|
+
<linearGradient id="seven" x1="0" y1="0" x2="1" y2="1">
|
|
8
|
+
<stop offset="0" stop-color="#ffe28f"/>
|
|
9
|
+
<stop offset="1" stop-color="#efa62b"/>
|
|
10
|
+
</linearGradient>
|
|
11
|
+
</defs>
|
|
12
|
+
|
|
13
|
+
<rect x="5" y="5" width="118" height="118" rx="26" fill="url(#bg)"/>
|
|
14
|
+
<path d="M22 24h84M22 96h84" stroke="#7fd8ff" stroke-opacity="0.10" stroke-width="2"/>
|
|
15
|
+
|
|
16
|
+
<!-- Terminal window: PowerShell prompt chevron + cursor. -->
|
|
17
|
+
<rect x="18" y="30" width="54" height="40" rx="9" fill="#0a2942" stroke="#5fd2ff" stroke-width="3"/>
|
|
18
|
+
<path d="M29 43l10 8-10 8" fill="none" stroke="#a6ecff" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
19
|
+
<path d="M45 59h13" stroke="#a6ecff" stroke-width="5" stroke-linecap="round"/>
|
|
20
|
+
|
|
21
|
+
<!-- PowerShell 7 numeral, drawn as a path so no font is required. -->
|
|
22
|
+
<path d="M76 30h36L94 90H83l15-50H76z" fill="url(#seven)"/>
|
|
23
|
+
|
|
24
|
+
<!-- Routing line: the prompt path leads to PowerShell 7. -->
|
|
25
|
+
<circle cx="24" cy="105" r="4" fill="#5fd2ff"/>
|
|
26
|
+
<path d="M30 105h58" fill="none" stroke="#ffcf5c" stroke-width="4" stroke-linecap="round"/>
|
|
27
|
+
<path d="M83 100l7 5-7 5" fill="none" stroke="#ffcf5c" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
|
28
|
+
</svg>
|
package/index.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
// dsh-plugin-pwsh7 — host plugin entry.
|
|
2
|
+
//
|
|
3
|
+
// Routes the model-facing `pwsh` tool to PowerShell 7 and exposes a `shell_router` tool for
|
|
4
|
+
// status, diagnosis, and per-user installation. The executor class lives in
|
|
5
|
+
// ./lib/executors/pwsh.mjs and is mounted by ./cordis.patch.yml.
|
|
6
|
+
|
|
7
|
+
import { defineTool } from './lib/shipped.mjs';
|
|
8
|
+
import { configPath, getRouterState, refreshRouterState } from './lib/state.mjs';
|
|
9
|
+
import { ensurePwsh } from './lib/install.mjs';
|
|
10
|
+
import { describeStatus, runDiagnosis } from './lib/status.mjs';
|
|
11
|
+
|
|
12
|
+
export const name = 'pwsh7-router';
|
|
13
|
+
export const inject = ['tools', 'systemPrompt'];
|
|
14
|
+
|
|
15
|
+
function formatBytes(bytes) {
|
|
16
|
+
if (!Number.isFinite(bytes) || bytes <= 0) return '0 B';
|
|
17
|
+
if (bytes >= 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GiB`;
|
|
18
|
+
if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MiB`;
|
|
19
|
+
if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KiB`;
|
|
20
|
+
return `${bytes} B`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Build the `shell_router` tool definition. Exported so tests/smoke harnesses can compile and
|
|
25
|
+
* inspect it without registering a second copy in the live tool registry.
|
|
26
|
+
*/
|
|
27
|
+
export function createToolDefinition(ctx) {
|
|
28
|
+
return defineTool({
|
|
29
|
+
name: 'shell_router',
|
|
30
|
+
description: [
|
|
31
|
+
'Inspect or repair how this Windows deployment routes the `pwsh` tool.',
|
|
32
|
+
'`status` (default) reports the resolved PowerShell 7 executable and version, the config file, and the active sandbox policy.',
|
|
33
|
+
'`diagnose` runs real PowerShell 7 commands under the current sandbox policy and reports failures with concrete hints.',
|
|
34
|
+
'`install` downloads and installs PowerShell 7 per-user under %LOCALAPPDATA%\\dsh\\pwsh7 (no admin), or falls back to winget.',
|
|
35
|
+
'Use it when `pwsh` reports that PowerShell 7 is missing, or when the user asks to set up PowerShell 7.',
|
|
36
|
+
].join(' '),
|
|
37
|
+
parameters: {
|
|
38
|
+
action: {
|
|
39
|
+
type: 'string',
|
|
40
|
+
enum: ['status', 'diagnose', 'install'],
|
|
41
|
+
description: 'Operation to perform; omit for `status`.',
|
|
42
|
+
},
|
|
43
|
+
method: {
|
|
44
|
+
type: 'string',
|
|
45
|
+
enum: ['auto', 'portable', 'winget'],
|
|
46
|
+
description: 'For `install`: installation strategy. `auto` prefers the per-user portable zip.',
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
output: {
|
|
50
|
+
schema: {
|
|
51
|
+
type: 'object',
|
|
52
|
+
additionalProperties: false,
|
|
53
|
+
properties: {
|
|
54
|
+
ok: { type: 'boolean', required: true, description: 'Whether the requested operation succeeded.' },
|
|
55
|
+
summary: { type: 'string', required: true, description: 'Human-readable status or diagnosis.' },
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
render: (_args, value) => [{ type: 'text', text: value.summary }],
|
|
59
|
+
},
|
|
60
|
+
async execute(args, exec) {
|
|
61
|
+
const action = args.action ?? 'status';
|
|
62
|
+
if (action === 'status') {
|
|
63
|
+
const state = await refreshRouterState();
|
|
64
|
+
return { ok: true, summary: await describeStatus(ctx, state, exec) };
|
|
65
|
+
}
|
|
66
|
+
if (action === 'diagnose') {
|
|
67
|
+
const state = await refreshRouterState();
|
|
68
|
+
const result = await runDiagnosis(ctx, state, exec);
|
|
69
|
+
return { ok: result.ok, summary: result.summary };
|
|
70
|
+
}
|
|
71
|
+
const progress = [];
|
|
72
|
+
let lastProgressAt = 0;
|
|
73
|
+
const installed = await ensurePwsh({
|
|
74
|
+
method: args.method ?? 'auto',
|
|
75
|
+
onProgress: (bytes, totalBytes) => {
|
|
76
|
+
const at = Date.now();
|
|
77
|
+
if (at - lastProgressAt < 4000) return;
|
|
78
|
+
lastProgressAt = at;
|
|
79
|
+
if (Number.isFinite(totalBytes) && totalBytes > 0) {
|
|
80
|
+
progress.push(`download progress: ${formatBytes(bytes)} / ${formatBytes(totalBytes)} (${Math.round((bytes / totalBytes) * 100)}%)`);
|
|
81
|
+
} else if (Number.isFinite(bytes) && bytes > 0) {
|
|
82
|
+
progress.push(`downloaded ${formatBytes(bytes)}`);
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
signal: exec.signal,
|
|
86
|
+
});
|
|
87
|
+
const state = await refreshRouterState();
|
|
88
|
+
const result = installed.result ?? {};
|
|
89
|
+
const resultLine = result.ok
|
|
90
|
+
? `pwsh: ok (${result.method ?? 'unknown'}${result.version === undefined ? '' : `, ${result.version}`}${result.path === undefined ? '' : ` at ${result.path}`})`
|
|
91
|
+
: `pwsh: failed — ${result.message ?? installed.message ?? 'unknown error'}`;
|
|
92
|
+
const summary = [
|
|
93
|
+
installed.ok ? 'PowerShell 7 install completed.' : 'PowerShell 7 install finished with problems.',
|
|
94
|
+
resultLine,
|
|
95
|
+
...(Array.isArray(installed.notes) ? installed.notes : []),
|
|
96
|
+
...progress.slice(-8),
|
|
97
|
+
'',
|
|
98
|
+
await describeStatus(ctx, state, exec),
|
|
99
|
+
].join('\n');
|
|
100
|
+
return { ok: installed.ok === true, summary };
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function apply(ctx) {
|
|
106
|
+
ctx.systemPrompt.section({
|
|
107
|
+
name: 'tool:pwsh7-router',
|
|
108
|
+
order: ctx.systemPrompt.getSectionOrder('TOOL_PWSH') + 1,
|
|
109
|
+
text: [
|
|
110
|
+
'On Windows this deployment routes the `pwsh` tool to PowerShell 7.',
|
|
111
|
+
'If PowerShell 7 is missing or misconfigured, call `shell_router`: action `status` reports the resolved executable,',
|
|
112
|
+
'`diagnose` runs real commands under the current sandbox policy, and `install` downloads a per-user copy into',
|
|
113
|
+
'%LOCALAPPDATA%\\dsh\\pwsh7 (no administrator rights) with a winget fallback.',
|
|
114
|
+
'A shell installed this way becomes usable without restarting the app.',
|
|
115
|
+
].join(' '),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
ctx.tools.register(createToolDefinition(ctx));
|
|
119
|
+
|
|
120
|
+
ctx.on('app-boot/config-reload', () => {
|
|
121
|
+
// Detection is cheap and must never break the host reload path.
|
|
122
|
+
refreshRouterState().catch(() => {});
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export { configPath, getRouterState };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// PowerShell 7 executor row for Windows.
|
|
2
|
+
//
|
|
3
|
+
// The shipped SandboxPwshExecutor keeps every sandbox behavior (escalation fields, denial
|
|
4
|
+
// facts, jobs, output caps) but resolves its executable once from config/well-known locations.
|
|
5
|
+
// This subclass replaces only that resolution: an explicit row `config.pwshPath` still wins,
|
|
6
|
+
// otherwise the shared router state resolves PowerShell 7 (and raises a setup hint when absent).
|
|
7
|
+
|
|
8
|
+
import { shipped } from '../shipped.mjs';
|
|
9
|
+
import { requirePwshPath } from '../state.mjs';
|
|
10
|
+
|
|
11
|
+
const { SandboxPwshExecutor } = shipped('@deepseek-ai/dsh-pwsh-sandbox');
|
|
12
|
+
|
|
13
|
+
export default class WindowsRouterPwshExecutor extends SandboxPwshExecutor {
|
|
14
|
+
/** @returns {string} resolved PowerShell 7 executable. */
|
|
15
|
+
get pwshPath() {
|
|
16
|
+
const declared = this.config?.pwshPath?.get?.();
|
|
17
|
+
if (typeof declared === 'string' && declared.trim().length > 0) return declared;
|
|
18
|
+
return requirePwshPath();
|
|
19
|
+
}
|
|
20
|
+
}
|