dsh-pwsh-direct
DSH Desktop(Windows)内置 pwsh/shell 工具"空结果"bug 的修复插件,提供两层能力:
- 自愈(根治):每次 DSH 启动时把已安装的
dsh-subprocess-local(安装目录副本 + 全部 profile 副本)打上补丁,在沙箱 runner 的 spawn 环境注入ELECTRON_RUN_AS_NODE=1。重启 DSH Desktop 后内置pwsh/shell即恢复。 pwsh_direct兜底工具(绕路):在重启生效前的窗口期、或 DSH 升级冲掉补丁后,可直接用pwsh_directspawn PowerShell 可执行文件,完全绕过 DSH 子进程服务层。
版本 0.2.0(2026-08-19,全链路验证通过)。可分发 zip 由 release/dsh-pwsh-direct 源码重建(zip 不入库,重建步骤见 release/README.md),包已具备 npm 发布条件(见"npm 发布")。
症状(本插件修复的对象)
DSH Desktop(服务器内嵌于 Electron 主进程)中调用内置 pwsh/shell 时返回完全空结果:命令没有执行、没有任何副作用(文件不创建、进程不启动),GUI 显示空白或 "(no output)"。
需求 / 兼容性
| 项 | 要求 |
|---|---|
| 平台 | Windows(自愈的安装副本目标是 Program Files 路径;pwsh_direct 的 PATH 解析与进程树杀法面向 Windows) |
| DSH Desktop | 服务器内嵌 Electron 主进程、bundle 解包到 resources\app.asar.unpacked 的当前标准安装形态 |
| Node | >= 20(见 package.json 的 engines) |
| PowerShell | pwsh 7+ 优先;缺失时回退 Windows PowerShell 5.1(含 System32 规范路径) |
| 写权限 | DSH Desktop 安装目录 + ~/.dsh/profiles/*(实测安装目录可写;若出现 write-failed 需以管理员身份操作一次) |
| 必需 peer(声明为 optional) | @deepseek-ai/dsh-tools —— 由 DSH 宿主环境(dsh CLI 树 / profile)提供,不随本插件捆绑;npm 已发布 0.0.1-rc.1,但声明为 optional peer 后 pnpm/npm 不自动安装(避免注入 shadow copy 遮蔽宿主捆绑版本);所有锚点均解析失败时插件自动禁用,不阻塞 DSH 启动 |
| 可选 peer(声明为 optional) | @chaggle/dsh-powershell-check —— 同 profile 安装时为 pwsh_direct 启用"PowerShell 坑位"执行门禁;缺失时门禁降级为透传并告警(当前未发布到 npm,经 github / 本地路径安装) |
安装
dsh plugin --profile web add <插件目录路径>
# 然后重启 DSH Desktop(必须)
--profile必填。本地路径生成link:依赖;声明dsh.bundle.patch的包由reconcilePlugins追加进dsh.profile.bundles层栈。- 必须重启:自愈是文件补丁,运行中的服务器模块缓存仍持有旧代码,重启后内置工具才恢复。
- 发布 zip 解压后可直接使用(内含
dsh-pwsh-direct目录)。 - 发布到 npm 后也可按包名直接安装(无 scope 公共包):
dsh plugin --profile web add dsh-pwsh-direct - 或直接按 GitHub 仓库安装(免克隆):
dsh plugin --profile web add github:0x1a27/dsh-pwsh-direct
卸载:
dsh plugin --profile web remove dsh-pwsh-direct
npm 发布
- 依赖建模:
@deepseek-ai/dsh-tools(npm 已发布0.0.1-rc.1)与@chaggle/dsh-powershell-check(当前未发布到 npm)均声明为 optional peerDependencies(peerDependenciesMeta.optional: true)。两者都由 DSH 宿主环境提供(见"依赖解析"),本插件不捆绑;pnpm/npm 不自动安装 optional peer,避免向 profile 的node_modules注入独立副本、遮蔽宿主捆绑版本(否则锚点①/②会先命中注入副本,产生版本漂移)。缺失时由 4 锚点回落 + 自动禁用机制兜底,安装不会失败。 - tarball 内容(
files字段控制):lib/、cordis.patch.yml+ npm 自动包含的package.json、README.md、LICENSE(共 6 个文件,~16 kB)。调试 / 验证脚本(smoke-test.mjs、verify/、release/)不发布。 - 发布流程:
npm login npm pack # 预览 tarball(应为 6 个文件) npm publish # 无 scope 包,默认 public - 发布后用户可按包名安装(见"安装");本地路径 / zip 安装方式继续可用,互不影响。
生产验收清单
依次执行,全部通过即达到生产条件:
- 自愈状态(任意机器,无需重启):
预期:JSON results 中每一项(install 副本 + 各 profile 副本)均为node lib\electron-runner-heal.mjspatched或already-patched。出现write-failed→ 见排障。 - 重启 DSH Desktop,在任意会话调用内置
pwsh(如Write-Output ping): 预期:返回非空结果。 pwsh_direct兜底路径:新会话调用pwsh_direct { "command": "Write-Output ok" }: 预期:ok。- 深度验证(可选;脚本内嵌本机路径):
注意:node smoke-test.mjs # 3 场景冒烟:预期 SMOKE TEST PASSED node verify\verify-spawn.mjs # S1/S2 A/B:env 有/无 ELECTRON_RUN_AS_NODE node verify\verify-runner.mjs # 1:1 生产链:预期 R1 exit=0 out=[RUNNER-OK-42]smoke-test.mjs内嵌本机路径常量(开头PLUGIN_DIR/CLI_BIN/DSH_ROOT默认值),换机器运行前须改成对应路径;verify\*.mjs依赖本机C:\Program Files\DSH Desktop\DSH Desktop.exe路径。
工作原理
根因链(2026-08-19 本机实测)
- DSH 服务器内嵌于 DSH Desktop(Electron)主进程,无独立 node 进程。生产链实际加载的是安装目录的
C:\Program Files\DSH Desktop\resources\app.asar.unpacked\node_modules\@deepseek-ai\dsh-subprocess-local——profile 副本与捆绑@deepseek-ai/dsh包内的 trampoline adapter 均为死代码。 - windows-acl 沙箱链以
process.execPath(DSH Desktop Electron exe)作为 runner 的 argv[0]。 dsh-subprocess-local的spawnSubprocess(const env = childEnv(spec.env);处)从不注入ELECTRON_RUN_AS_NODE=1。- 结果:每次沙箱化命令都启动一个第二个桌面应用实例;第二实例丢失单例锁后静默自杀(exit 0 / 零输出 / 零副作用)→ 内置
pwsh/shell表现为"完全空结果"。
验证证据(verify/ 下脚本):
- S1/S2 A/B(
verify-spawn.mjs):node spawn exe +ELECTRON_RUN_AS_NODE=1→ 探针以 node 语义运行(exit 0、输出正常、写入 marker);同条件去掉 env → 无输出、无副作用(第二 GUI 实例自杀)。 - 1:1 生产链(
verify-runner.mjs):exe-as-node + 生产 windows-acl runner(app.asar.unpacked里的dsh-sandbox-windows-acl)+ 捆绑 koffi + 真实 pwsh → exit 0、输出RUNNER-OK-42,与重启后生产链一致。
自愈细节
目标(
lib/electron-runner-heal.mjs,healAll):- 安装副本:
C:\Program Files\DSH Desktop\resources\app.asar.unpacked\node_modules\@deepseek-ai\dsh-subprocess-local\lib\index.js及C:\Program Files (x86)\...同路径; - 各 profile:
~/.dsh/profiles/<name>/node_modules/@deepseek-ai/dsh-subprocess-local/lib/index.js。
- 安装副本:
补丁点:
spawnSubprocess内const env = childEnv(spec.env);一行之后插入(带 marker 注释、可检测可重入;路径比较不区分大小写):/* dsh-desktop-powershell-fix: ...(5 行注释)... */ if (process.versions.electron !== void 0 && program.toLowerCase() === process.execPath.toLowerCase()) env.ELECTRON_RUN_AS_NODE = "1";幂等与安全:已含 marker
dsh-desktop-powershell-fix的文件跳过(already-patched);任一步失败只告警、不抛错、绝不阻塞 DSH 启动。手动 / 独立执行:
node lib/electron-runner-heal.mjs—— 打印各副本状态(JSON)。状态值:
status 含义 patched本次运行新打上补丁 already-patched已含 marker anchor-not-found未找到 spawnSubprocess锚点(上游布局变了?)unreadable/write-failed无读 / 写权限 not-found(install 副本)两个标准安装路径都不存在 升级冲掉:DSH Desktop 升级会重写
app.asar.unpacked;插件下次启动时重新自愈,无需人工干预。窗口期(升级后至下次启动前)内置工具仍为空——用pwsh_direct。
pwsh_direct 工具
| 项 | 说明 |
|---|---|
| 工具名 | pwsh_direct(不能与内置 pwsh 同名,重名注册会抛错) |
| 参数 | command(必填)/ description / workdir / timeoutMs(默认 120s,上限 600s) |
| 可执行解析 | pwsh.exe(PS 7+,PATH 优先)→ powershell.exe(PATH)→ System32\WindowsPowerShell\v1.0\powershell.exe |
| 启动参数 | -NoLogo -NoProfile -NonInteractive -Command,stdin 忽略,stdout/stderr 分路捕获(每路保留最后 256KB) |
| 编码 | 执行前注入 [Console]::OutputEncoding/InputEncoding = UTF-8(try 包裹),修复 PS 5.1 zh-CN 宿主的 GBK 中文乱码(R1 坑位) |
| 超时 | 到点 taskkill /F /T 杀进程树(非 Windows 回落 SIGKILL),输出 [timeout after Xms: process tree killed] |
| 输出格式 | stdout(空则 (no output))→ [stderr] 段(非空时)→ [exit code: N](非零时),与内置 pwsh 同格式 |
| 坑位门禁 | 可解析到 @chaggle/dsh-powershell-check(任一锚点)时,复用其 checker 作 tools/pre-execute deny 门禁(与内置 pwsh 同一规则);不可解析时门禁降级透传并告警 |
| 系统提示 | 注入 tool:dsh-pwsh-direct 段,提示模型内置 pwsh 失败时切换 pwsh_direct |
依赖解析(4 锚点回落,绝不阻塞 DSH 启动)
lib/index.js 的 require 链按以下锚点顺序回落解析 @deepseek-ai/dsh-tools,任一命中即继续;全部失败 → 告警并跳过注册(插件禁用,DSH 照常启动):
- 当前模块自身锚点(插件所在 profile 的
node_modules); ~/.dsh/profiles/<active>/node_modules(活动 profile 锚点);process.execPath所在 Electron 安装树的node_modules(捆绑依赖锚点);- 内置
require(CommonJS 默认解析,兜底)。
0.1.x 在 link: 安装形态下 import 即抛错、整个 DSH 无法启动的问题由此根治。
安全与副作用
- 修改的文件:仅安装目录与各 profile 下的
dsh-subprocess-local/lib/index.js——各插入 6 行(5 行注释 + 1 行if),带 markerdsh-desktop-powershell-fix;不修改其他任何文件,无网络访问,无遥测。 - 写入安装目录:自愈会写
C:\Program Files\DSH Desktop\...(实测可写;不可写时对应副本报write-failed,需管理员权限运行一次)。 pwsh_direct无沙箱:它以 DSH 进程的环境与权限直接 spawn PowerShell 可执行文件,不受内置工具的文件沙箱约束。命令以 DSH 进程权限执行——高危操作请与对待内置 pwsh 同等谨慎。- 系统提示注入:一段
tool:dsh-pwsh-direct(指导模型选择工具,无其他行为)。
卸载与回滚
dsh plugin --profile web remove dsh-pwsh-direct
- 卸载并重启后:
pwsh_direct工具、门禁与系统提示段消失。 - 文件补丁保留:已补丁的
ELECTRON_RUN_AS_NODE注入仅在"argv[0] 是 Electron 宿主二进制"时生效,对普通 node runner 无害,且幂等可检测。 - 要彻底回滚:在目标文件中删除 marker 注释块与
if (process.versions.electron ...)一行,或重装 DSH Desktop(升级会自然覆盖)。
排障
| 症状 | 可能原因 | 处理 |
|---|---|---|
重启后内置 pwsh 仍空 |
安装副本未打上(write-failed),或重启未真正生效 |
node lib\electron-runner-heal.mjs 看 JSON;write-failed 则以管理员身份启动 DSH Desktop 一次并重启;全部 already-patched 时再重启一次 |
宿主日志 cannot resolve "@deepseek-ai/dsh-tools" |
插件未装到提供该 peer 的 profile | dsh plugin --profile <name> add <path> 装到正确 profile |
宿主日志 pitfall gate disabled |
profile 未装 @chaggle/dsh-powershell-check |
同 profile 安装 checker;或接受透传(不阻断) |
heal 结果 anchor-not-found |
上游 dsh-subprocess-local 布局变更 |
人工检查目标文件 spawnSubprocess 函数,或上报 |
heal 结果 not-found(install) |
DSH Desktop 安装目录非标准(两个 Program Files 路径都不命中) |
确认实际安装路径;profile 副本自愈不受影响 |
| DSH 升级后补丁被冲掉 | 预期行为(升级重写 app.asar.unpacked) |
下次启动自愈;窗口期用 pwsh_direct |
| GUI 显示 "No result provided" 且会话查询类工具(search/recall 等)失败 | 会话索引引擎(dsh-session-query-sqlite)的独立故障,与本 bug 无关 |
重启通常恢复;与 DSH Desktop 的服务器重启瞬态故障窗口有关 |
已知限制
workdir默认取 DSH 宿主进程 cwd,不是会话工作目录——操作会话文件时请显式传workdir。- 命令由解析到的 PowerShell 版本解释:回落 5.1 时
&&/||/??/三元?:等语法不可用(坑位门禁会拦截)。 - 安装副本自愈只覆盖
C:\Program Files\DSH Desktop两个标准路径,非标准安装目录需人工处理(profile 副本不受影响)。 - 自愈是本地文件补丁、非上游修复:DSH Desktop 升级后补丁被冲掉属预期,插件下次启动重新自愈;若上游将来官方修复此 bug,可停用本插件的自愈(
pwsh_direct工具可保留作绕路)。 pwsh_direct命令运行在 DSH 沙箱之外(见"安全与副作用")。
文件
| 文件 | 说明 |
|---|---|
lib/index.js |
插件入口(启动自愈 / pwsh_direct 注册 / 4 锚点依赖解析 / 门禁 / 系统提示) |
lib/electron-runner-heal.mjs |
自愈模块(healProfileFile / healAllProfiles / healInstallFile / healAll,可独立执行) |
cordis.patch.yml |
bundle patch 声明(dsh.plugin 安装契约) |
LICENSE |
MIT 许可(随 npm tarball 自动包含) |
smoke-test.mjs |
3 场景冒烟测试(进程内功能 / 启动模拟 / 坏环境不抛;内嵌本机路径) |
verify/verify-spawn.mjs |
S1/S2 A/B(node spawn exe,env 有/无) |
verify/verify-runner.mjs |
1:1 生产链(exe-as-node + 生产 windows-acl runner + 捆绑 koffi + pwsh) |
变更日志
- 0.2.0(2026-08-19)
- 新增启动自愈
electron-runner-heal(覆盖安装副本 + 全部 profile 副本;幂等、绝不阻塞启动、可独立执行) - 依赖解析改为 4 锚点回落(旧版在
link:安装下 import 即抛错、阻塞 DSH 启动;现降级为告警并跳过注册) - 自愈补丁行的锚点比较改为不区分大小写
- 新增"PowerShell 坑位"执行门禁(复用
@chaggle/dsh-powershell-check,可选) - npm 发布就绪:两个 peer 声明为 optional peerDependencies(不捆绑、不自动安装、缺失自动禁用),补
LICENSE,移除未使用的 esbuild devDependency
- 新增启动自愈
- 0.1.0:初版——
pwsh_direct兜底执行工具。
License
MIT
No comments yet. Be the first to write one.