🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

更新守卫 jiuyuan-yi/dsh-update-guard

DeepSeek Harness
jiuyuan-yi3 天前有提交已提供中文文档扫描:含敏感能力

更新前排查插件兼容性,启动失败自动修复

30.6/100
综合分
1
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/11/2 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add jiuyuan-yi/dsh-update-guard
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:仅索引
是什么[目录]—
dsh 原生插件 · other
暂无时间戳·判据 →
装得上吗[实装验证]—
本站尚未做安装检查
暂无时间戳·判据 →
安全吗[自动扫描]—
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
数据截至 2026/11/04·判据 →
还在维护吗[仓库]✓
活跃:最近一次提交在 3 天前
暂无时间戳·判据 →
图例:✓ 通过! 需注意— 未判定 / 无数据· 「未判定」= 本站尚无数据,不代表插件有问题
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →

可信度证据

安全扫描自动 · 静态分析源码
v2.7.0
🔵 扫描:含敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件发起外部网络请求
查看扫描证据(共 119 处,展示前 84 条)
  • 读取本地文件
    lib/index.js:146
    if (!existsSync(p)) return undefined
  • 读取本地文件
    lib/index.js:147
    return JSON.parse(readFileSync(p, 'utf8'))
  • 发起网络请求
    lib/client.js:73
    fetch(API + '/config.json', { cache: 'no-store' })
  • 发起网络请求
    lib/client.js:89
    return fetch(API + '/config.json', {
  • 发起网络请求
    lib/client.js:148
    fetch(API + '/report.json', { cache: 'no-store' })
  • 读取本地文件
    lib/run.mjs:25
    if (explicit && existsSync(explicit)) return explicit
  • 读取本地文件
    lib/run.mjs:54
    if (!existsSync(p)) return { ...CONFIG_DEFAULTS }
  • 读取本地文件
    lib/run.mjs:56
    return { ...CONFIG_DEFAULTS, ...JSON.parse(readFileSync(p, 'utf8')) }
  • 写入本地文件
    lib/run.mjs:64
    writeFileSync(p, JSON.stringify({ ...CONFIG_DEFAULTS, ...config }, null, 2), 'utf8')
  • 读取本地文件
    lib/run.mjs:70
    if (!existsSync(p)) return undefined
  • 读取本地文件
    lib/run.mjs:72
    const j = JSON.parse(readFileSync(p, 'utf8'))
  • 写入本地文件
    lib/run.mjs:378
    mkdirSync(cacheRoot, { recursive: true })
  • 写入本地文件
    lib/run.mjs:380
    writeFileSync(p, JSON.stringify({ at: new Date().toISOString(), ...analysis }, null, 2), 'utf8')
  • 读取本地文件
    lib/launcher.mjs:36
    if (existsSync(c)) return c
  • 读取本地文件
    lib/launcher.mjs:71
    try { names = readdirSync(desk).filter(f => f.toLowerCase().endsWith('.lnk')) } catch { return undefined }
  • 读取本地文件
    lib/launcher.mjs:97
    const exes = readdirSync(root)
  • 读取本地文件
    lib/launcher.mjs:100
    .map(f => ({ name: f, size: statSync(join(root, f)).size }))
  • 执行外部命令固定命令
    lib/launcher.mjs:115
    const out = execFileSync('powershell', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command',
  • 读取本地文件
    lib/launcher.mjs:164
    if (!existsSync(linkPath)) return false
  • 执行外部命令固定命令
    lib/launcher.mjs:186
    execFileSync('powershell', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script], {
  • 读取本地文件
    lib/launcher.mjs:189
    return existsSync(linkPath) ? linkPath : undefined
  • 写入本地文件
    lib/launcher.mjs:794
    mkdirSync(dir, { recursive: true })
  • 写入本地文件
    lib/launcher.mjs:797
    writeFileSync(cmdPath, Buffer.from(CMD_BODY, 'ascii'))
  • 写入本地文件
    lib/launcher.mjs:800
    writeFileSync(vbsPath, Buffer.from(VBS_BODY, 'ascii'))
  • 写入本地文件
    lib/launcher.mjs:804
    writeFileSync(ps1Path, Buffer.concat([Buffer.from([0xEF, 0xBB, 0xBF]), Buffer.from(ps1, 'utf8')]))
  • 写入本地文件
    lib/launcher.mjs:828
    mkdirSync(bakDir, { recursive: true })
  • 写入本地文件
    lib/launcher.mjs:830
    if (!existsSync(bak)) copyFileSync(existing, bak)
  • 删除本地文件
    lib/launcher.mjs:969
    try { rmSync(f, { force: true }); removed.push(f) } catch { }
  • 删除本地文件
    lib/launcher.mjs:971
    try { rmSync(join(dir, '.generated.json'), { force: true }) } catch { }
  • 读取本地文件
    lib/restore.mjs:23
    try { names = readdirSync(profileDir) } catch { return [] }
  • 读取本地文件
    lib/restore.mjs:29
    try { mtime = statSync(p).mtimeMs } catch { }
  • 读取本地文件
    lib/restore.mjs:36
    const raw = readFileSync(path, 'utf8').replace(/^ FEFF/, '')
  • 读取本地文件
    lib/restore.mjs:53
    if (!existsSync(join(dir, 'package.json'))) return { ok: false, reason: '包未安装' }
  • 读取本地文件
    lib/restore.mjs:62
    if (!existsSync(join(dir, f))) return { ok: false, reason: `patch 文件缺失: ${f}` }
  • 读取本地文件
    lib/restore.mjs:125
    try { patchBytes = statSync(patchPath).size } catch { patchBytes = 0 }
  • 写入本地文件
    lib/restore.mjs:167
    copyFileSync(pkgPath, p)
  • 写入本地文件
    lib/restore.mjs:172
    copyFileSync(patchPath, p)
  • 写入本地文件
    lib/restore.mjs:181
    writeFileSync(pkgPath, JSON.stringify(manifest, null, 2), 'utf8')
  • 写入本地文件
    lib/restore.mjs:184
    copyFileSync(plan.patchBackup.path, patchPath)
  • 读取本地文件
    lib/diagnose.mjs:21
    const raw = readFileSync(path, 'utf8').replace(/^ FEFF/, '')
  • 读取本地文件
    lib/diagnose.mjs:29
    try { return statSync(path).mtimeMs } catch { return undefined }
  • 读取本地文件
    lib/diagnose.mjs:33
    try { return statSync(path).size } catch { return undefined }
  • 读取本地文件
    lib/diagnose.mjs:38
    return readdirSync(dir)
  • 读取本地文件
    lib/diagnose.mjs:101
    return list.every(f => existsSync(join(dir, f)))
  • 读取本地文件
    lib/installer.mjs:42
    try { if (statSync(candidate).isFile()) return { path: candidate, source } } catch { }
  • 执行外部命令固定命令
    lib/installer.mjs:60
    const out = execFileSync('where', [exe], { encoding: 'utf8', timeout: 5000, windowsHide: true })
  • 执行外部命令固定命令
    lib/installer.mjs:70
    const out = execFileSync('reg', ['query', key, '/v', 'Path'], { encoding: 'utf8', timeout: 5000, windowsHide: true })
  • 执行外部命令固定命令
    lib/installer.mjs:126
    const out = execFileSync('powershell', ['-NoProfile', '-NonInteractive', '-ExecutionPolicy', 'Bypass', '-Command', script], {
  • 读取本地文件
    lib/installer.mjs:129
    if (out && existsSync(join(out, 'resources', 'app-update.yml'))) return out
  • 读取本地文件
    lib/installer.mjs:137
    if (env && existsSync(join(env, 'resources', 'app-update.yml'))) return env
  • 读取本地文件
    lib/installer.mjs:145
    if (existsSync(join(dir, 'resources', 'app-update.yml'))) return dir
  • 读取本地文件
    lib/installer.mjs:161
    if (c && existsSync(join(c, 'resources', 'app-update.yml'))) return c
  • 读取本地文件
    lib/installer.mjs:169
    if (!existsSync(yml)) return undefined
  • 执行外部命令来自配置/变量
    lib/installer.mjs:219
    return execFileSync(sevenZip, args, {
  • 写入本地文件
    lib/installer.mjs:249
    mkdirSync(work, { recursive: true })
  • 删除本地文件递归
    lib/installer.mjs:290
    try { rmSync(work, { recursive: true, force: true }) } catch { }
  • 写入本地文件
    lib/installer.mjs:312
    mkdirSync(cacheRoot, { recursive: true })
  • 写入本地文件
    lib/installer.mjs:346
    writeFileSync(cacheFile, JSON.stringify(record, null, 2))
  • 读取本地文件
    lib/plugins.mjs:40
    try { entries = readdirSync(dir, { withFileTypes: true }) } catch { return }
  • 读取本地文件
    lib/plugins.mjs:44
    try { dirFlag = statSync(p).isDirectory() } catch { continue }
  • 读取本地文件
    lib/plugins.mjs:70
    if (statSync(file).size > 8_000_000) continue
  • 读取本地文件
    lib/plugins.mjs:71
    text = readFileSync(file, 'utf8')
  • 读取本地文件
    lib/plugins.mjs:110
    const isDir = (p) => { try { return statSync(p).isDirectory() } catch { return false } }
  • 读取本地文件
    lib/plugins.mjs:113
    if (!existsSync(root)) continue
  • 读取本地文件
    lib/patch.mjs:26
    if (!existsSync(p)) return []
  • 读取本地文件
    lib/patch.mjs:28
    try { text = readFileSync(p, 'utf8') } catch { return [] }
  • 读取本地文件
    lib/patch.mjs:39
    return readFileSync(p, 'utf8')
  • 读取本地文件
    lib/patch.mjs:134
    if (!existsSync(patchPath)) throw new Error('cordis.patch.yml not found in profile')
  • 读取本地文件
    lib/patch.mjs:141
    if (existsSync(statePath)) {
  • 写入本地文件
    lib/patch.mjs:164
    writeFileSync(tmp, next, { encoding: 'utf8' })
  • 写入本地文件
    lib/patch.mjs:165
    renameSync(tmp, patchPath)
  • 写入本地文件
    lib/patch.mjs:166
    writeFileSync(statePath, JSON.stringify(state, null, 2), 'utf8')
  • 读取本地文件
    lib/patch.mjs:181
    if (!existsSync(statePath)) return { restored: [], missing: [] }
  • 写入本地文件
    lib/patch.mjs:241
    writeFileSync(tmp, lines.join(eol), { encoding: 'utf8' })
  • 写入本地文件
    lib/patch.mjs:242
    renameSync(tmp, patchPath)
  • 写入本地文件
    lib/patch.mjs:243
    writeFileSync(statePath, JSON.stringify({ version: 1, target: null, entries: [] }, null, 2), 'utf8')
  • 读取本地文件
    lib/compat.mjs:41
    const m = JSON.parse(readFileSync(pkgPath, 'utf8').replace(/^ FEFF/, ''))
  • 读取本地文件
    lib/compat.mjs:53
    if (!existsSync(manifestPath)) continue
  • 读取本地文件
    lib/compat.mjs:55
    try { manifest = JSON.parse(readFileSync(manifestPath, 'utf8').replace(/^ FEFF/, '')) } catch { continue }
  • 读取本地文件
    lib/compat.mjs:93
    const m = JSON.parse(readFileSync(join(profileDir, 'package.json'), 'utf8').replace(/^ FEFF/, ''))
  • 读取本地文件
    lib/compat.mjs:101
    try { return statSync(dir).isDirectory() } catch { return false }
  • 读取本地文件
    lib/services.mjs:118
    try { entries = readdirSync(d, { withFileTypes: true }) } catch { return }
  • 读取本地文件
    lib/services.mjs:134
    const t = readFileSync(f, 'utf8')
  • 读取本地文件
    lib/asar.mjs:13
    const buf = readFileSync(path)
扫描于 2026/11/4(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

社区实装还没有人打卡 · 装过的第一个就是你
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

DeepSeek Harness 更新守卫:更新前检查插件兼容性,启动失败可自愈(测试版)

README

由 DeepSeek 最新模型翻译生成
dsh-update-guard · 更新守卫 给 DeepSeek Harness 桌面端用的插件:更新前检查插件会不会崩,崩了能自己修回来。 起因很具体:一次版本更新后,界面加载到某个插件就不再出现,日志里只有一句 is not a function。更新器问了「要现在安装吗」,但没有任何东西检查过这个更新会不会把已装的插件打断。 两条腿 一、更新前检查 应用下载完更新、还没安装的时候: 1. 从安装包里解出两套接口面 - 客户端:共享 UI 模块导出了哪些成员(浏览器半侧用) - 宿主:运行时提供了哪些服务名(纯宿主插件用) 2. 扫描已装插件实际用到什么 3. 只报告用到了新版本里没有的东西的插件 命中后可以自动锁定——在 cordis.patch.yml 里把那个插件的条目设为 disabled: true。锁的是配置项,逐字节可还原。 两层证据,严格程度不同: | 证据 | 缺失后果 | 自动锁定 | | 客户端成员(UiPrimitives.xxx) | 渲染时抛错,界面崩 | ✅ 会锁 | | 宿主服务(inject: [...] 里的服务名) | 插件激活不了;若是 required 插件,整个应用起不来 | ❌ 只提示 | 宿主服务不自动锁,因为服务可能以扫描器没建模的方式动态提供,误报会锁掉正常插件。报出来,你自己判断。
展开完整文档(README 剩余 1 节)
dsh-update-guard · 更新守卫

给 DeepSeek Harness 桌面端用的插件:更新前检查插件会不会崩,崩了能自己修回来。

起因很具体:一次版本更新后,界面加载到某个插件就不再出现,日志里只有一句 is not a function。更新器问了「要现在安装吗」,但没有任何东西检查过这个更新会不会把已装的插件打断。

两条腿

一、更新前检查

应用下载完更新、还没安装的时候:

1. 从安装包里解出两套接口面
- 客户端:共享 UI 模块导出了哪些成员(浏览器半侧用)
- 宿主:运行时提供了哪些服务名(纯宿主插件用)
2. 扫描已装插件实际用到什么
3. 只报告用到了新版本里没有的东西的插件

命中后可以自动锁定——在 cordis.patch.yml 里把那个插件的条目设为 disabled: true。锁的是配置项,逐字节可还原。

两层证据,严格程度不同:

| 证据 | 缺失后果 | 自动锁定 |
| 客户端成员(UiPrimitives.xxx) | 渲染时抛错,界面崩 | ✅ 会锁 |
| 宿主服务(inject: [...] 里的服务名) | 插件激活不了;若是 required 插件,整个应用起不来 | ❌ 只提示 |

宿主服务不自动锁,因为服务可能以扫描器没建模的方式动态提供,误报会锁掉正常插件。报出来,你自己判断。

没有解压器也不会失败:降级成「只比版本声明」(engines.dsh / peerDependencies),这本来就是加载器自己的检查。

二、崩了能修

在桌面挂一个启动包装器(wscript + 隐藏窗口,不带控制台):

- 启动失败 → 重试 → 仍然失败就弹框问你要不要停用第三方插件(默认按钮是「否」)
- 发现插件配置被清空(比如点了官方的「禁用第三方插件」)→ 从备份还原,只跳过有问题的那个
- 快捷方式被改回直连 exe → 下次启动自动改回来
- 同一时刻只允许一个启动器;DSH 已在运行时不会起第二个进程

安装

需要 Node.js 18+、Windows、DeepSeek Harness 桌面端。

1. 放进 profile 的 node_modules
git clone <repo> "%USERPROFILE%\.dsh\profiles\desktop\node_modules\dsh-update-guard"

// 2. 编辑 <profile>/package.json,把它加进 bundle 列表 {
"dsh": {
"profile": {
"bundles": [ "...", "dsh-update-guard" ]
}
}
}

重启应用。设置 → 通用 里会出现三行开关。

命令行

node bin/guard.mjs check # 检查待安装的更新
node bin/guard.mjs check --json # 机器可读
node bin/guard.mjs lock # 锁定命中的插件
node bin/guard.mjs unlock # 撤销全部锁定
node bin/guard.mjs restore # 预演:配置被重置后会还原成什么样
node bin/guard.mjs restore --apply
node bin/guard.mjs config --set autoLock --value true
node bin/guard.mjs status

启动器(自愈腿)
node bin/guard.mjs launcher status # 现在接管了什么
node bin/guard.mjs launcher restore --dry-run # 预演:会把图标指回哪
node bin/guard.mjs launcher restore # 把快捷方式指回应用,删掉生成的脚本
node bin/guard.mjs launcher uninstall # 上面这一步 + 配置改回 installLauncher=false

退出码:0 正常,2 发现不兼容,1 出错。

配置

<profile>/.dsh-update-guard.json

| 键 | 默认 | 说明 |
| preflightOnStartup | true | 启动时跑检查 |
| autoLock | false | 命中直接锁定,不询问 |
| maxAutoLock | 3 | 一次最多锁几个 |
| autoRestore | true | 配置被清空时从备份还原 |
| installLauncher | false | 是否接管桌面快捷方式(自愈腿)。默认关闭;打开会改桌面图标,关掉会把图标还原 |

覆盖范围

两类插件都要能看见,否则等于只查了一半:

| 插件类型 | 依赖什么 | 用哪套接口面查 |
| 有界面组件 | 共享 UI 模块的成员 | 客户端 |
| 纯宿主 | inject: [...] 里的服务名 | 宿主 |

宿主服务名从安装包的 app.asar 里提取,识别三种注册形式( super(ctx,"name")、.provide("name")、.set("name",…) 以及

egisterService/defineService/static service),并额外收集运行时里出现过的字符串常量作宽松兜底——宁可漏报,不可误报。

scripts/、	est/、docs/ 和 client.js(浏览器半侧)不计入宿主面。

检查状态(不把「没查」说成「通过」)

面板和 CLI 会明确区分四种状态:

| 状态 | 含义 |
| 完整检查 | 版本声明 + 接口面都比对过 |
| 部分检查 | 只比对了版本声明(没有解压器,或解包失败) |
| 发现风险 | 有插件命中,附清单 |
| 没有待装更新 | 当前没有已下载的更新 |

部分检查不会显示成「没有发现不兼容」。

测试用的环境变量

启动器读三个环境变量,只用于测试与无人值守:

| 变量 | 作用 |
| DSH_LAUNCHER_PROFILE | 指向别的 profile |
| DSH_LAUNCHER_EXE / DSH_LAUNCHER_ARGS | 指向别的可执行文件与参数 |
| DSH_LAUNCHER_ANSWER | 预置询问答案(yes/no),不弹框 |
| DSH_LAUNCHER_QUIET=1 | 不弹任何对话框,只写日志 |
| DSH_LAUNCHER_TEST_HIDE_WINDOW=1 | 测试专用:连应用窗口也隐藏。与 QUIET 分开,免得 QUIET 把主界面藏掉 |

解压器

NSIS 外壳只有 7-Zip 系能读。探测顺序:

1. 环境变量 DSH_7Z / DSH_7ZA
2. 包内 vendor/7z.exe
3. PATH 上的 7z / 7za / 7zz
4. 注册表 HKLM\SOFTWARE\7-Zip\Path
5. 常见目录(7-Zip、NanaZip)

都没有就降级到版本声明检查。设 DSH_UPDATE_GUARD_NO_EXTRACTOR=1 可强制降级。

Windows 自带的 tar.exe(libarchive)能读内层 app-*.7z,但读不了 NSIS 外壳——实测报 Unrecognized archive format。

它不会做什么

- 不联网,不上报任何数据
- 不改插件代码,只改 cordis.patch.yml 里的 disabled 字段
- 不碰 !!js 之类的条件值(那是加载器的动态表达式,改写会破坏语义)
- 不判断自己
- 不写死任何绝对路径:安装目录从注册表 InstallLocation 问,profile 从 DSH_PROFILE_DIR 或 ~/.dsh/profiles/<名> 取

测试

node test/all.mjs # 一次跑全部;跳过会单独标 UNVERIFIED,不算通过
node test/launcher-flow.mjs # 假应用驱动真实启动器:正常启动/失败/恢复失败后再启动
node test/diagnose-agreement.mjs # 诊断结论必须与启动器的真实行为一致
node test/tier-merge.mjs # 两层结果必须合并且互不覆盖
node test/cache-hit.mjs # 缓存真的命中,不重复解包
node test/launcher-repair.mjs # 损坏配置 / 结构缺失 / 主动精简 / 被重置 四种情况
node test/compat.mjs # 版本范围与声明检查
node test/restore.mjs # 外科式还原
node test/routes.mjs # 面板接口
node test/client-bundle.mjs # 浏览器半侧(假 window / 假 require)
node test/fresh-machine.mjs # 虚构 profile:验证不夹带任何特定插件
node test/patch-safety.mjs # patch 改写安全
node test/plugin-smoke.mjs # 插件加载
node test/manifest.mjs # dsh.client 声明按加载器规则校验
node test/e2e.mjs # 真解包端到端

e2e 需要一份待安装的更新和能解包的安装目录;没有时会打印 SKIP 并以 0 退出。

真实环境验证

模拟测试只证明流程逻辑。真实升级那天按 docs/real-env-checklist.md 逐条走。

已知限制

- 只在 Windows 上验证过(启动包装器依赖 wscript.exe)
- 「接口面比对」只看 4 个共享 UI 模块,宿主侧 API 改动不在覆盖范围
- 从没在真实版本升级上跑过完整流程
- 自动还原的判定依据是启动器自己记录的上次清单 + 补丁层证据;没有记录(首次运行)时不做任何推断
- 还原时的插件校验只做结构检查(包在、package.json 可解析、声明了 dsh.bundle、patch 文件在),不启动插件验证运行时行为:结构完整但接口不兼容的插件,还原后仍可能出问题
- 接口面比对只覆盖 4 个共享 UI 模块 + 宿主服务名;宿主侧 API 的行为变化(签名、语义)不在范围内

许可

MIT
预览分享卡片长什么样 ↓jiuyuan-yi/dsh-update-guard
分享 · DeepSeek Harness Hub
jiuyuan-yi/dsh-update-guard

更新前排查插件兼容性,启动失败自动修复

这个插件是你维护的吗?
作者本人可认领并核对本条目;认领通过后,详情页会显示「作者已认领」标记。
认领条目 →
回到顶部