@wenbin_wb/dsh-bridge 2.12.0 → 2.12.1
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/CHANGELOG.md +21 -0
- package/lib/index.js +71 -5
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,27 @@
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [v2.12.1] - 2026-10-03
|
|
8
|
+
|
|
9
|
+
> 本版为**桌面版(Electron)兼容修复**版。排查依据为一手来源:官方同仓库 `apps/desktop-host/src/index.ts`、`apps/desktop/README.zh.md` 及官方 `evaluatePluginCompatibility` 实测。核心功能(面板、IM、隧道、存储)在桌面端与 Web 版跑同一套宿主与前端,确认兼容;以下两处是"Web 正常、桌面走错路"的分支,已修。
|
|
10
|
+
|
|
11
|
+
### 🐞 修复
|
|
12
|
+
|
|
13
|
+
- **面板"重启 DSH"在桌面端拦截**:桌面宿主由 Electron 壳经 IPC 管理生死,插件若按 Web/CLI 假设派生 `restart-helper` 重拉进程,会产生壳外孤儿(抢占 19387 端口、触发壳的恢复对话框)。现 `restartDsh` 开头检测桌面环境(`process.versions.electron` 或主模块路径 `desktop-host`,fail-open),直接拒绝并指引走应用菜单重启,绝不派生任何子进程。
|
|
14
|
+
- **插件自升级 profile 参数化**:`upgradePlugin` 原来硬编码 `dsh plugin --profile web add`,在桌面端(profile 名为 `desktop`)会把包装进宿主不加载的 profile,造成"显示成功实际没升"。现按宿主形态推导(桌面 `desktop`、其余 `web`),传入值白名单只认 `desktop`/`web`(防 shell 注入)。
|
|
15
|
+
- **桌面端一键升级 DSH 文案**:`_probeDshUpgrade` 在桌面端仍正确返回不可升级(应用包内文件不能用 npm 动),但 reason 换成桌面 relevant 的指引(走应用菜单"检查更新")。
|
|
16
|
+
|
|
17
|
+
### 🧪 测试
|
|
18
|
+
|
|
19
|
+
- 新增 `test/desktop-host-compat.test.mjs`(11 项:electron 标记判定、`desktop-host` 路径分段判定、fail-open 脏输入、profile 推导、桌面重启零派生、非桌面重启不受影响、默认 web/显式 desktop/注入 profile 白名单,以及"测试文件名含子串不误判"的回归)。全量 **395 项测试 100% 通过**。
|
|
20
|
+
|
|
21
|
+
### 📝 升级说明
|
|
22
|
+
|
|
23
|
+
- 无破坏性变更;Web/CLI 用户行为与 v2.12.0 完全一致(桌面检测 fail-open,判不准时按原路走)。
|
|
24
|
+
- 桌面端实机验证仍未做(本机无桌面版安装包):P0/P1 修复基于官方源码的静态结论 + 单测,`process.versions.electron` 在 RunAsNode 下的存在性为 Electron 官方行为。建议在 macOS/Windows 桌面端实测"面板重启被拒"与"插件升级进 desktop profile"两条路径后再关闭该跟踪。
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
7
28
|
## [v2.12.0] - 2026-10-03
|
|
8
29
|
|
|
9
30
|
> 本版为**安全加固 + 首次启用引导 + 发布包瘦身**版。依据外部安全审计(针对 v2.10.13)逐条核实后修复;按「不破坏现有用户」原则,**默认监听地址保持 `0.0.0.0` 不变**,改用引导提示降低「开箱敞开」风险。
|
package/lib/index.js
CHANGED
|
@@ -317,6 +317,49 @@ export function detectSupervisor({ env = process.env, cgroup = '' } = {}) {
|
|
|
317
317
|
return { kind: 'self', reason: '未检出托管器(无 DSH_DAEMON/PM2_HOME,也不在 systemd 单元内)' };
|
|
318
318
|
}
|
|
319
319
|
|
|
320
|
+
/**
|
|
321
|
+
* 判断当前宿主是否跑在 DSH 桌面版(Electron)内(纯函数,便于单测)。
|
|
322
|
+
*
|
|
323
|
+
* 背景:桌面端宿主是 Electron 以 RunAsNode 模式拉起的同一套 @deepseek-ai/dsh,
|
|
324
|
+
* 进程生死由 Electron 壳经 IPC 管理(shutdown/quit-inspection),没有 systemd、
|
|
325
|
+
* DSH_DAEMON/PM2 这类托管器。插件若按 Web/CLI 版的假设自行重启/升级宿主,
|
|
326
|
+
* 会拉起脱离壳管理的孤儿进程(抢占 19387 端口、触发壳的恢复对话框)或把包
|
|
327
|
+
* 装进错误的 profile(桌面端 profile 名为 `desktop`,不是 `web`)。
|
|
328
|
+
*
|
|
329
|
+
* 判据(按可靠性排序,任一命中即判为桌面端):
|
|
330
|
+
* 1. `process.versions.electron` 存在 —— Electron(含 RunAsNode 模式)的官方
|
|
331
|
+
* 行为保证该字段存在,跨平台最可靠;
|
|
332
|
+
* 2. 主模块路径含 `desktop-host` —— 桌面宿主入口固定为 desktop-host 包。
|
|
333
|
+
*
|
|
334
|
+
* fail-open:判不准时一律按 Web/CLI 版走,绝不能把正常用户的重启/升级拦掉。
|
|
335
|
+
*
|
|
336
|
+
* @param {object} [opts]
|
|
337
|
+
* @param {object} [opts.versions] process.versions(默认 process.versions)
|
|
338
|
+
* @param {string} [opts.argv1] process.argv[1](默认 process.argv[1])
|
|
339
|
+
* @returns {boolean}
|
|
340
|
+
*/
|
|
341
|
+
export function isDesktopHost({ versions = process.versions, argv1 = process.argv[1] } = {}) {
|
|
342
|
+
try {
|
|
343
|
+
if (versions && typeof versions.electron === 'string' && versions.electron.length > 0) return true;
|
|
344
|
+
} catch { /* 忽略,继续下一个判据 */ }
|
|
345
|
+
try {
|
|
346
|
+
// 必须按路径分段匹配(前后是分隔符或串 ends):避免用户目录名恰好包含
|
|
347
|
+
// desktop-host 子串(如本仓库的 test/desktop-host-compat.test.mjs)时误判。
|
|
348
|
+
// 生产路径形如 .../dsh-desktop-host/lib/index.js。
|
|
349
|
+
if (typeof argv1 === 'string' && /(?:^|[/\\])(?:dsh-)?desktop-host(?:[/\\]|$)/i.test(argv1)) return true;
|
|
350
|
+
} catch { /* 忽略,fail-open */ }
|
|
351
|
+
return false;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* 当前宿主加载的 profile 名(纯函数,便于单测)。
|
|
356
|
+
* 桌面端为 `desktop`,其余(Web/CLI)为 `web`。供 `dsh plugin --profile <name> add`
|
|
357
|
+
* 类命令使用,避免把包装进宿主不加载的 profile。
|
|
358
|
+
*/
|
|
359
|
+
export function currentProfileName(opts = {}) {
|
|
360
|
+
return isDesktopHost(opts) ? 'desktop' : 'web';
|
|
361
|
+
}
|
|
362
|
+
|
|
320
363
|
class ProxyServer {
|
|
321
364
|
constructor({ localPort, targetPort, authManager, logger, allowedOrigins }) {
|
|
322
365
|
this.localPort = localPort;
|
|
@@ -1183,7 +1226,11 @@ class BridgeService {
|
|
|
1183
1226
|
}
|
|
1184
1227
|
return {
|
|
1185
1228
|
upgradable: false,
|
|
1186
|
-
|
|
1229
|
+
// 桌面端 dsh 装在 App 包内:走桌面应用内更新,不要用 npm 动它。
|
|
1230
|
+
// 桌面检测是 fail-open 的,判不准时仍是这条通用文案,不影响 Web/CLI 用户。
|
|
1231
|
+
reason: isDesktopHost()
|
|
1232
|
+
? '桌面版 DSH 由应用内更新统一管理(一键升级仅支持 npm 全局安装的 CLI 版);请用桌面应用菜单的"检查更新"升级,勿用 npm 改动应用包内文件'
|
|
1233
|
+
: 'dsh 非标准 npm 全局安装(Electron/打包/源码/pnpm 等),无法自动升级;请按官方渠道手动更新',
|
|
1187
1234
|
};
|
|
1188
1235
|
} catch {
|
|
1189
1236
|
return { upgradable: false, reason: '探测 dsh 安装形态失败,请按官方渠道手动更新' };
|
|
@@ -1244,7 +1291,9 @@ class BridgeService {
|
|
|
1244
1291
|
}
|
|
1245
1292
|
|
|
1246
1293
|
// 一键直接升级插件(执行 dsh / npx / npm 自动升级,使用安全的参数数组彻底杜绝 shell 注入)
|
|
1247
|
-
|
|
1294
|
+
// opts.profile 供测试注入;默认按当前宿主推导(桌面端为 `desktop`,其余为 `web`),
|
|
1295
|
+
// 绝不能写死 --profile web——桌面端会把包装进宿主不加载的 profile,造成"显示成功实际没升"。
|
|
1296
|
+
async upgradePlugin({ version, spawnImpl = spawn, profile } = {}) {
|
|
1248
1297
|
const targetVersion = version ? String(version).trim() : 'latest';
|
|
1249
1298
|
// 严格 SemVer 白名单正则校验
|
|
1250
1299
|
if (!/^(latest|\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?)$/.test(targetVersion)) {
|
|
@@ -1283,9 +1332,15 @@ class BridgeService {
|
|
|
1283
1332
|
const siblingNpm = join(nodeDir, isWin ? 'npm.cmd' : 'npm');
|
|
1284
1333
|
const siblingNpx = join(nodeDir, isWin ? 'npx.cmd' : 'npx');
|
|
1285
1334
|
|
|
1335
|
+
// 目标 profile:显式传入优先,否则按宿主形态推导(桌面端 `desktop`,其余 `web`)。
|
|
1336
|
+
// 桌面端 profile 名固定,见官方 desktop-host(runProfile profile: 'desktop')。
|
|
1337
|
+
// 安全:payload 里的 profile 不可信,白名单只认 desktop/web,其余一律按推导值处理,
|
|
1338
|
+
// 避免未校验字符串进入 shell:true 的 spawn 参数(参数拼接有注入风险)。
|
|
1339
|
+
const targetProfile = (profile === 'desktop' || profile === 'web') ? profile : currentProfileName();
|
|
1340
|
+
|
|
1286
1341
|
const tasks = [
|
|
1287
|
-
{ cmd: 'dsh', args: ['plugin', '--profile',
|
|
1288
|
-
{ cmd: existsSync(siblingNpx) ? siblingNpx : 'npx', args: ['--yes', '@deepseek-ai/dsh', 'plugin', '--profile',
|
|
1342
|
+
{ cmd: 'dsh', args: ['plugin', '--profile', targetProfile, 'add', pkgSpec] },
|
|
1343
|
+
{ cmd: existsSync(siblingNpx) ? siblingNpx : 'npx', args: ['--yes', '@deepseek-ai/dsh', 'plugin', '--profile', targetProfile, 'add', pkgSpec] },
|
|
1289
1344
|
{ cmd: existsSync(siblingNpm) ? siblingNpm : 'npm', args: ['install', pkgSpec] },
|
|
1290
1345
|
];
|
|
1291
1346
|
|
|
@@ -1296,7 +1351,7 @@ class BridgeService {
|
|
|
1296
1351
|
const res = await new Promise((resolve, reject) => {
|
|
1297
1352
|
let cp;
|
|
1298
1353
|
try {
|
|
1299
|
-
cp =
|
|
1354
|
+
cp = spawnImpl(task.cmd, task.args, {
|
|
1300
1355
|
windowsHide: true,
|
|
1301
1356
|
shell: true,
|
|
1302
1357
|
env: augmentedEnv,
|
|
@@ -1517,6 +1572,17 @@ class BridgeService {
|
|
|
1517
1572
|
// 并把全过程写入 ~/.dsh/dsh-bridge/restart.log(失败不再无声)。
|
|
1518
1573
|
// opts 仅供单测注入(spawnImpl / readCgroup / env / scheduleExit)。
|
|
1519
1574
|
async restartDsh(opts = {}) {
|
|
1575
|
+
// 桌面端拦截(P0):桌面宿主由 Electron 壳经 IPC 管理生死,插件绝不能自行
|
|
1576
|
+
// 派生 helper 重拉进程——那会产生壳外孤儿(抢占 19387 端口、触发壳的恢复
|
|
1577
|
+
// 对话框)。直接拒绝,并指引用户走应用菜单重启。
|
|
1578
|
+
// isDesktopHost 是 fail-open 的:判不准时按 Web/CLI 版走,不拦正常用户。
|
|
1579
|
+
if (isDesktopHost({ versions: opts.processVersions, argv1: opts.argv1 })) {
|
|
1580
|
+
return {
|
|
1581
|
+
ok: false,
|
|
1582
|
+
error: '当前运行在 DSH 桌面版内,宿主由应用统一管理,插件不能自行重启。请用桌面应用菜单的重启/退出重进(有关闭确认保护正在跑的任务),不要用面板重启。',
|
|
1583
|
+
desktopManaged: true,
|
|
1584
|
+
};
|
|
1585
|
+
}
|
|
1520
1586
|
const env = opts.env ?? process.env;
|
|
1521
1587
|
const spawnImpl = opts.spawnImpl ?? spawn;
|
|
1522
1588
|
const readCgroup = opts.readCgroup
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wenbin_wb/dsh-bridge",
|
|
3
|
-
"version": "2.12.
|
|
3
|
+
"version": "2.12.1",
|
|
4
4
|
"description": "手机扫码即可在移动端/公网继续用 DeepSeek Harness,人不在电脑前也能接着干。一键局域网二维码、Cloudflare 公网隧道、自建隧道与微信 / QQ / 飞书 / Telegram Bot(多工作区/会话持久化/媒体/卡片审批/流式输出),无需自己搭公网服务器。",
|
|
5
|
-
"releaseNotes": "【v2.12.
|
|
5
|
+
"releaseNotes": "【v2.12.1】桌面版(Electron)兼容修复\n• 修复:面板\"重启 DSH\"在桌面端拦截——桌面宿主由应用壳管理,插件不再自行派生重启(避免壳外孤儿进程与端口冲突),指引走应用菜单重启\n• 修复:插件自升级 profile 参数化——桌面端装进 desktop profile,不再硬编码 web(避免\"显示成功实际没升\")\n• 优化:桌面端一键升级 DSH 的降级文案指向应用内更新",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "lib/index.js",
|
|
8
8
|
"exports": {
|