在 Web UI 中一键管理 DeepSeek Harness DSH…
54.2/100
综合分
71
GitHub 星标
112
npm 周下载
—/5
用户评分
数据截至 2026/9/20 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-web-plugin-managernpm 源 · dsh-web-plugin-manager 的发布者账号已校验与本仓库一致
状态速览
判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态信任档位:仅索引
是什么
dsh 原生插件 · market
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
更新放缓:最近一次提交在 44 天前
档位由哪些信号合成
本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过展开查看检查明细 ↓
以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。
✓npm 包dsh-web-plugin-manager @ 0.6.3
✓Node 引擎未声明 engines.node
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明
验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/30 02:38:11
安全扫描自动 · 静态分析源码v2.7.0🔵 扫描:含敏感能力尚无人工评估展开查看扫描说明与证据 ↓
安全扫描自动 · 静态分析源码
v2.7.0静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。
可执行外部命令读写/删除本地文件读取剪贴板或密钥环境变量发起外部网络请求
查看扫描证据(共 153 处,展示前 95 条)
- 读取本地文件dist/index.js:78
const cached = JSON.parse(readFileSync(marketplaceCachePaths().cachePath, 'utf8')); - 写入本地文件dist/index.js:93
mkdirSync(dirname(cachePath), { recursive: true }); - 写入本地文件dist/index.js:98
writeFileSync(tmpPath, JSON.stringify({ - 写入本地文件dist/index.js:104
renameSync(tmpPath, cachePath); - 删除本地文件dist/index.js:106
rmSync(failurePath, { force: true }); - 读取本地文件dist/index.js:110
const parsed = JSON.parse(readFileSync(marketplaceCachePaths().failurePath, 'utf8')); - 写入本地文件dist/index.js:122
mkdirSync(dirname(failurePath), { recursive: true }); - 写入本地文件dist/index.js:123
writeFileSync(failurePath, JSON.stringify({ fetchedAt: new Date().toISOString(), message }, undefined, 2) + '\n'); - 读取本地文件dist/index.js:186
if (existsSync(dir)) - 写入本地文件dist/index.js:192
mkdirSync(dir, { recursive: true }); - 读取本地文件dist/index.js:227
if (!existsSync(oldDir)) - 读取本地文件dist/index.js:232
if (existsSync(newDir)) - 读取本地文件dist/index.js:257
if (!existsSync(dir)) - 执行外部命令来自配置/变量dist/index.js:319
const child = spawn(exec.command, exec.args, { - 删除本地文件递归dist/index.js:1344
rmSync(target, { recursive: true, force: true }); - 删除本地文件递归dist/index.js:1350
rmSync(record.location, { recursive: true, force: true }); - 发起网络请求dist/client.js:4890
const response = await fetch(`${BASE}/${op}`, { - 读取本地文件dist/cli.js:117
if (!existsSync(dir)) { - 读取本地文件dist/cli.js:152
if (!existsSync(dir)) { - 读取本地文件dist/cli.js:173
if (isUnderRoot(target, root) && existsSync(target)) { - 删除本地文件递归dist/cli.js:174
rmSync(target, { recursive: true, force: true }); - 读取本地文件dist/cli.js:179
if (removed === 0 && record.location !== null && isUnderRoot(record.location, root) && existsSync(record.location)) { - 删除本地文件递归dist/cli.js:180
rmSync(record.location, { recursive: true, force: true }); - 读取本地文件dist/cli.js:207
if (!existsSync(dir)) { - 读取本地文件dist/cli.js:245
if (!existsSync(dir)) { - 写入本地文件dist/patch.js:497
writeFileSync(tmp, content, 'utf8'); - 写入本地文件dist/patch.js:498
renameSync(tmp, patchPath); - 读取本地文件dist/live.js:310
return readFileSync(path, 'utf8'); - 读取本地文件dist/kinds.js:84
return readdirSync(dir, { withFileTypes: true }); - 读取本地文件dist/kinds.js:152
const manifest = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')); - 读取本地文件dist/kinds.js:188
const has = (name) => existsSync(join(root, name)); - 读取本地文件dist/kinds.js:195
const manifest = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')); - 读取本地文件dist/kinds.js:226
const text = readFileSync(join(root, manifest.name), 'utf8'); - 写入本地文件dist/kinds.js:253
mkdirSync(destRoot, { recursive: true }); - 删除本地文件递归dist/kinds.js:266
rmSync(dest, { recursive: true, force: true }); - 写入本地文件dist/kinds.js:288
mkdirSync(destRoot, { recursive: true }); - 删除本地文件递归dist/kinds.js:298
rmSync(dest, { recursive: true, force: true }); - 读取本地文件dist/kinds.js:337
const data = JSON.parse(readFileSync(kindRecordsFile(), 'utf8')); - 写入本地文件dist/kinds.js:346
mkdirSync(join(kindRecordsFile(), '..'), { recursive: true }); - 写入本地文件dist/kinds.js:355
writeFileSync(tmp, JSON.stringify({ version: 1, records: data }, undefined, 2) + '\n'); - 写入本地文件dist/kinds.js:356
renameSync(tmp, target); - 写入本地文件dist/kinds.js:432
mkdirSync(join(blockedReposFile(), '..'), { recursive: true }); - 删除本地文件递归dist/kinds.js:490
rmSync(target, { recursive: true, force: true }); - 读取本地文件dist/presets.js:55
if (!existsSync(path)) - 读取本地文件dist/presets.js:60
hash.update(readFileSync(path)); - 读取本地文件dist/presets.js:80
return JSON.parse(readFileSync(path, 'utf8')); - 读取本地文件dist/presets.js:95
if (existsSync(marker)) { - 读取本地文件dist/presets.js:104
entries = readdirSync(presetDir); - 读取本地文件dist/presets.js:127
if (existsSync(marker)) { - 写入本地文件dist/presets.js:255
mkdirSync(archiveRoot, { recursive: true }); - 写入本地文件dist/presets.js:340
writeFileSync(marker, JSON.stringify(record, undefined, 2) + String.fromCharCode(10), 'utf8'); - 发起网络请求dist/net.js:46
agent = new ProxyAgent(proxy); - 发起网络请求dist/net.js:75
return await undiciFetch(url, { - 读取密钥环境变量dist/registry.js:152
const token = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN; - 读取密钥环境变量dist/registry.js:194
const token = process.env.GITHUB_TOKEN ?? process.env.GH_TOKEN; - 读取本地文件dist/registry.js:231
const raw = JSON.parse(readFileSync(registryCacheFile(), 'utf8')); - 写入本地文件dist/registry.js:248
mkdirSync(dirname(registryCacheFile()), { recursive: true }); - 写入本地文件dist/registry.js:249
writeFileSync(registryCacheFile(), JSON.stringify({ - 读取本地文件dist/registry.js:308
const data = JSON.parse(readFileSync(dshSoCacheFile(), 'utf8')); - 写入本地文件dist/registry.js:347
mkdirSync(dirname(dshSoCacheFile()), { recursive: true }); - 写入本地文件dist/registry.js:348
writeFileSync(dshSoCacheFile(), JSON.stringify({ savedAt: new Date().toISOString(), entries }) + '\n'); - 读取本地文件dist/scan.js:50
entries = await readdir(current, { withFileTypes: true }); - 读取本地文件dist/scan.js:70
const text = await readFile(file, 'utf8'); - 读取本地文件dist/paths.js:14
const manifest = JSON.parse(readFileSync(join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'), 'utf8')); - 读取本地文件dist/paths.js:47
if (!existsSync(path)) - 读取本地文件dist/paths.js:50
return JSON.parse(readFileSync(path, 'utf8')); - 读取本地文件dist/paths.js:69
return readFileSync(patchPath(dir), 'utf8'); - 读取本地文件dist/paths.js:170
const manifest = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')); - 读取本地文件dist/childproc.js:17
if (statSync(candidate).isFile()) { - 读取本地文件dist/childproc.js:18
accessSync(candidate, constants.X_OK); - 读取本地文件dist/childproc.js:30
if (existsSync(nextToNode)) { - 读取本地文件dist/childproc.js:41
for (const entry of readdirSync(versions, { withFileTypes: true })) { - 读取本地文件dist/childproc.js:45
if (existsSync(join(bin, name))) - 执行外部命令来自配置/变量dist/childproc.js:57
const output = execFileSync(comspec, ['/d', '/s', '/c', 'chcp 65001 >nul & where ' + name], { - 执行外部命令来自配置/变量dist/childproc.js:141
execFile(exec.command, exec.args, { cwd, timeout: 10 * 60 * 1000, maxBuffer: 4 * 1024 * 1024, env: commandEnv(tool.dir, env), ...(exec.verbatim ? { wi - 执行外部命令来自配置/变量dist/childproc.js:169
execFile(exec.command, exec.args, { timeout: timeoutMs, maxBuffer: 4 * 1024 * 1024, windowsHide: true, env: commandEnv(tool.dir), ...(exec.verbatim ? - 执行外部命令固定命令dist/profiles.js:24
const output = execFileSync('powershell', ['-NoProfile', '-NonInteractive', '-Command', script], { - 读取本地文件dist/profiles.js:60
entries = readdirSync('/proc'); - 读取本地文件dist/profiles.js:71
cmdline = readFileSync('/proc/' + entry + '/cmdline', 'utf8'); - 执行外部命令固定命令dist/profiles.js:95
: (procProcessLines() ?? execFileSync('ps', ['-eo', 'pid=,args='], { encoding: 'utf8' }).split('\n')); - 执行外部命令固定命令dist/profiles.js:214
spawn('osascript', ['-e', 'tell application "Terminal" to do script "' + final.replace(/"/g, '\\"') + '"'], { stdio: 'ignore' }).unref(); - 执行外部命令固定命令dist/profiles.js:234
spawn('cmd', ['/c', 'start', '', 'cmd', '/k', command], { stdio: 'ignore', windowsHide: true, env }).unref(); - 执行外部命令来自配置/变量dist/profiles.js:250
spawn(bin, argv, { stdio: 'ignore', windowsHide: true }).unref(); - 执行外部命令来自配置/变量dist/profiles.js:262
execFileSync(process.platform === 'win32' ? 'where' : 'which', [bin], { stdio: 'ignore', windowsHide: true }); - 写入本地文件dist/profiles.js:356
writeFileSync(path, JSON.stringify(manifest, null, 2) + '\n'); - 读取本地文件dist/marketplaceMerge.js:362
for (const entry of readdirSync(dir, { withFileTypes: true })) { - 读取本地文件dist/marketplaceMerge.js:594
if (!existsSync(dir)) - 读取本地文件dist/installFlow.js:76
const manifest = JSON.parse(readFileSync(join(dir, 'node_modules', name, 'package.json'), 'utf8')); - 读取本地文件dist/installFlow.js:304
const lock = readFileSync(join(profileDir(profile), 'pnpm-lock.yaml'), 'utf8'); - 写入本地文件dist/installFlow.js:361
mkdirSync(cacheRoot, { recursive: true }); - 读取本地文件dist/installFlow.js:368
const created = !existsSync(dest); - 读取本地文件dist/installFlow.js:386
if (existsSync(join(pkgDir, 'package.json'))) { - 读取本地文件dist/installFlow.js:388
const manifest = JSON.parse(readFileSync(join(pkgDir, 'package.json'), 'utf8')); - 读取本地文件dist/installFlow.js:401
if (!existsSync(join(pkgDir, 'package.json'))) { - 删除本地文件递归dist/installFlow.js:765
rmSync(prepared.spec, { recursive: true, force: true });
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包
静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/cordis-plugin-loader@deepseek-ai/dsh-client-locale@deepseek-ai/dsh-client-store@deepseek-ai/dsh-client-ui-primitives@deepseek-ai/dsh-client-ui-renderer@deepseek-ai/dsh-client-ui-settings@deepseek-ai/dsh-client-ui-slots@deepseek-ai/dsh-host-webserver@deepseek-ai/dsh-llm@deepseek-ai/dsh-tools@deepseek-ai/schemastery给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
功能亮点
- 查看插件
- 实时启停插件
- 安装卸载插件
展开完整介绍(110 字)↓
在 Web UI 中一键管理 DeepSeek Harness (DSH) 插件:查看、实时启停、安装/卸载、更新检测、健康检查(依赖/冲突/兼容性分析)、环境管理、插件市场。bundle 与非 bundle 插件全覆盖
README
由 DeepSeek 最新模型翻译生成dsh-web-plugin-manager
⚠️ 本仓库已停止维护(对应 DSH <= 0.1.5-rc.2)
DSH 0.1.6-alpha.2 起请改用 dsh-plugin-manager-companion。
原因:官方 0.1.6-alpha.2 自带完整插件管理页(@deepseek-ai/dsh-client-ui-plugin-manager + @deepseek-ai/dsh-plugin-manager 宿主服务),本仓库原先「遮蔽官方只读页 + 自建 profile 写权」的路线不再成立。而且官方在 dsh-base bundle 里新增了 id: plugin-manager 行,与本仓库 cordis.patch.yml 的行 id 撞车,在 0.1.6-alpha.2 上会让 profile 完全无法启动(TypeError: duplicate loader entry id: plugin-manager),服务名也与官方同名冲突。
新仓库改为伴生补强路线:当前环境的写操作全部交还官方通道,只补官方明说不做的部分(多环境管理、agent 预设组合、普通插件模块的文件操作、版本/更新、安装前质量门、深度诊断)。
本仓库的 0.6.x 仍可用于 DSH <= 0.1.5-rc.2;在 0.1.6-alpha.2 及以上请勿安装。
展开完整文档(README 剩余 1 节)
dsh-web-plugin-manager
⚠️ 本仓库已停止维护(对应 DSH <= 0.1.5-rc.2)
DSH 0.1.6-alpha.2 起请改用 dsh-plugin-manager-companion。
原因:官方 0.1.6-alpha.2 自带完整插件管理页(@deepseek-ai/dsh-client-ui-plugin-manager + @deepseek-ai/dsh-plugin-manager 宿主服务),本仓库原先「遮蔽官方只读页 + 自建 profile 写权」的路线不再成立。而且官方在 dsh-base bundle 里新增了 id: plugin-manager 行,与本仓库 cordis.patch.yml 的行 id 撞车,在 0.1.6-alpha.2 上会让 profile 完全无法启动(TypeError: duplicate loader entry id: plugin-manager),服务名也与官方同名冲突。
新仓库改为伴生补强路线:当前环境的写操作全部交还官方通道,只补官方明说不做的部分(多环境管理、agent 预设组合、普通插件模块的文件操作、版本/更新、安装前质量门、深度诊断)。
本仓库的 0.6.x 仍可用于 DSH <= 0.1.5-rc.2;在 0.1.6-alpha.2 及以上请勿安装。
| English
在 Web UI 中一键管理 DeepSeek Harness (DSH) 插件:查看、实时启停、安装/卸载、更新检测、健康检查(依赖/冲突/兼容性分析)、环境管理、插件市场。bundle 与非 bundle 插件全覆盖。
强烈建议先装管理器、再装其他插件:本管理器自带质量门与健康检查(安装时全链依赖扫描 + bundle patch 行校验 + 安装即回滚;安装后可做依赖图/冲突/循环/peer 兼容性分析)。先装管理器,后续每个插件的安装都会经过检测,能显著减少「装上就炸、重启起不来」的情况。
若已存在未经验证的插件导致 profile 无法启动:先用 dsh --profile <name> --patch <empty.yml> 等方式排查,或手动清理对应依赖/cordis.patch.yml 行后,再安装管理器接管。
安装
方式一(推荐):从 npm 安装(务必带 @latest)
dsh plugin --profile <name> add dsh-web-plugin-manager@latest
方式二:从源码构建
cd /path/to/dsh-web-plugin-manager
pnpm install && pnpm run build
dsh plugin --profile <name> add .
重启 profile 后,Web UI 的「设置」会出现「插件管理」标签页、「技能与预设」与「市场」一级菜单。
更新
命令方式(推荐):升级到最新版(重写 specifier,质量门 + 失败自动回滚到旧版本) dshpm update dsh-web-plugin-manager --profile NAME 等价命令(pnpm 语义)
dsh plugin --profile NAME add dsh-web-plugin-manager@latest
UI 自更新:打开「设置 → 插件管理」,「检查更新」会列出全部已装插件(含管理器自身),有新版时点对应卡片上的「更新」即可——管理器可以一键更新自己,失败自动装回上一版本;更新后需重启 profile 生效。
注意:
- add 不带版本号不会升级:profile 里已声明旧版本(如 ^0.1.2)时,dsh plugin add(即 pnpm add)保留原 specifier 不升;dsh plugin update(即 pnpm update)也只在已声明的范围内重解析。跨版本升级必须用 add ...@latest 或 dshpm update。
- @latest 解析到旧版时:先查 pnpm config get registry 是否为镜像源(npmmirror 等 dist-tag 同步滞后 + pnpm 元数据缓存),用 pnpm add dsh-web-plugin-manager@latest --registry=https://registry.npmjs.org 强制走官方源;显式指定版本号(如 @0.3.8)永远可靠。
- pnpm 11 用户:pnpm 11 默认 minimumReleaseAge: 1440(24 小时)——发布不足 24 小时的版本会被扣住,导致「当天发版当天点更新装不上」或解析到旧版。在 profile 的 pnpm-workspace.yaml 中加 minimumReleaseAge: 0(或 minimumReleaseAgeExclude 白名单)即可;dsh 不会覆盖该文件,改动会保留。
CLI(dshpm)
用户让 AI 安装插件时,AI 默认会跑裸 dsh plugin add / pnpm add——这绕过了全部防护。本包提供 dshpm bin(随插件安装进入 profile 的 node_modules,也可 node <profile>/node_modules/dsh-web-plugin-manager/dist/cli.js 直接调用),所有变更走与 Web UI 完全相同的受保护链路(质量门 + 自动回滚 + 分析 + insert 行维护):
dshpm install [--env KEY=value ...] --profile <name> # npm 名 / github:user/repo / git URL / tarball / 本地路径 dshpm remove <name> --profile <name> # insert 行 + 包依赖一并清理 dshpm update <name> --profile <name> # 升级到 @latest(重写 specifier,质量门+回滚) dshpm mount <name> --profile <name> # 补挂载官方 CLI 手动安装的未挂载依赖 dshpm uninstall-kind <owner/repo> [--profile <name>] # 卸载市场安装的 skill/预设/cordis 插件 dshpm list --profile <name> # bundle 层栈 / 已装包 / insert 行 dshpm analyze --profile <name> # 健康检查,有问题退出码 1
git 源插件需要安装期环境变量时,命令行会打印缺失变量清单,并给出 --env KEY=value 续装命令(非交互式)。
功能
| 能力 | 说明 |
| 查看 | 合并展示层栈/依赖/挂载行/运行条目;手动安装未挂载的依赖显示「未挂载」并可一键挂载;agent 预设构成展示(官方 0.1.3 inventory 对齐,0.1.2 自动隐藏) |
| 实时启停/卸载 | managed 块编辑 patch,经 loader 直接应用,实时生效、重启后持久;删除已装插件(含 bundle)同步实时卸载其运行条目与遗留 managed 块,客户端启动表立即清除(删除后刷新不再加载已删的 bundle 脚本);禁用/卸载插件时按归属标记管理其自带 agent 预设(卸载清原版、用户改过的保留并报告;禁用零损失归档、重新启用自动恢复;归属标记 .dsh-preset-owner.json 中立标准 + 兼容 gamelike/dsh-agent-rp 既有标记,经宿主 agentPresets 服务删除) |
| 安装 | 官方 dsh plugin 命令行 + 质量门 + 自动回滚;非 bundle 自动写挂载行并实时加载;git 源自动 clone、已发布 npm 优先;多类型安装:skill(SKILL.md→~/.dsh/skills,热加载)与 agent 预设(agent.cordis.yml→~/.dsh/.agent-presets,官方发现机制直接可见)直装+记录;非插件/skill/预设仓库拒绝并加入市场屏蔽名单;git 源安装前扫描仓库所需环境变量(TOKEN/KEY/SECRET 形态),缺则暂停询问、行内填写后继续,answers 仅按扫描白名单注入(防 PATH/HOME 注入),宿主敏感键不传给第三方脚本 |
| 更新 | 检查更新(npm dist-tag / git HEAD / 安装 commit),更新带质量门与回滚;含管理器自身(自更新)——管理页可直接点更新升级,失败自动装回旧版本 |
| 健康检查 | 依赖图/缺失/循环/重复行 id/同名注册冲突(服务/工具/section/路由)/peer 版本/官方包重复;运行中追加 pending 与失败诊断及遮蔽自检;A 级问题一键修复、B 级建议确认后修复 |
| 环境管理 | 启停、复制/转移插件、创建/重命名/删除 profile(官方 profile 只读)、备份导出/导入恢复(差异对比 + 恢复前行内二次确认 + 切换目标自动重算差异;批量恢复与其他变更互斥) |
| 市场 | 静态索引(topic:dsh-plugin 全量 1.3 万+ 条,多源兜底 + gzip + 磁盘缓存 + 新鲜度门控)+ awesome 精选覆盖层 + dsh.so 独立验证/安全扫描徽标(L1-L5 + 风险等级叠加);服务端已安装判定(包名/repository 双向/git 源/目录探测);更新检测(索引版本对比);同名包冲突消解;分类筛选(上游 CI 分类器 id 聚合,服务端 categories 计数 + 客户端兜底);模糊搜索 + 相关性排序(plgmgr 命中 plugin-manager,官方 rankByName 算法长名适配版 + 字符掩码预筛);24h 缓存、超时预算、代理支持、失败负缓存 |
| agent 工具 | plugin_status/search/install/uninstall/toggle + 安装守卫(拦截裸命令并引导)+ 提示词注入;plugin_search 自然语言检索市场(name/topics/描述加权),结果提示安装前先浏览仓库 |
功能与限制的详细说明见 docs/feature-reference.md(随仓库与 npm 包发布)。
架构
- Host:src/index.ts —— PluginManagerService(ctx.pluginManager)+ /api2/plugin-manager/ REST 路由(带信任围栏:POST+JSON 强制、Host 回环/白名单校验、Origin 同源——防 CSRF/DNS-rebinding);安装链路 installWithSource→installProtected(质量门+回滚)整体串行互斥;七条长操作(install/update/remove/backupRestore/uninstallKind/copyPlugins/checkUpdates)REST job 化——POST 即返 jobId、客户端经 job op 轮询取结果(HTTP 超时不再与服务端状态脱节,短任务用 250ms 起步的轮询),在途 job 数上限背压堆叠点击
- 基础层:src/paths.ts —— profile 路径/manifest/patch 读取、全局变更互斥队列、宿主 profile 识别(argv → 安装位置兜底);src/childproc.ts —— 命令解析(运行中 node 目录 → PATH → $NVM_DIR 兜底)、PATH 注入子进程环境、官方 dsh plugin 运行器、异步 exec 工具(请求路径上零 execFileSync)
- 保护链路:src/installFlow.ts —— 安装/更新/删除保护流(源准备:git clone 缓存 + npm-first 探测;质量门 + 自动回滚;更新检查 npm dist-tag / git HEAD / lockfile commit;managed 行清理),全部经全局互斥串行
- 市场管道:src/marketplaceMerge.ts —— catalog/PLUGINS.md 抓取、registry 索引合并、星数富化、服务端已安装标记、dsh.so 徽标叠加、屏蔽名单过滤与同名包消解(index.ts 只保留缓存闭包与 REST 接线)
- profile 生命周期:src/profiles.ts —— 运行实例进程表扫描(3s TTL 缓存)、终端窗口启动、端口探测、新 profile 模板、in-box bundle 保护恢复
- 实时应用:src/live.ts —— 补丁变更先经 loader include 条目直接应用(entry.update,与平台 watchUserPatches 同通道)再写文件,绕开平台级死锁(watcher 刷新增量卸载 HMR 依赖的 timer 行造成循环等待);补偿平台 applyEntryPatches 对 patch 对象的原地改写(深克隆 + 烘焙值归一化);插件自有的 patch 文件 watcher 让手动编辑持续实时生效;删除包时按行名卸载其全部 loader 行(remove-by-name,容忍烘焙字段),防止残留客户端条目引用已删除的 bundle 脚本
- 分析引擎:src/analyze.ts —— 离线依赖图/冲突/兼容性分析(与质量门共享扫描器);运行时诊断读取 ctx.reflect 活跃服务表
- Patch 编辑:src/patch.ts —— managed 标记块追加/移除(insert/disable 双类型识别)、行级操作、原子写入(tmp + rename);处理 YAML 陷阱(@ 包名引号、空数组文档、纯注释文件恢复模板)
- 环境变量扫描:src/scan.ts —— git 源安装期 env 需求扫描(2 层/40 文件/8 变量上限)+ 子进程 env 敏感键剔除;src/installSession.ts 会话状态机(15 分钟 TTL、answers 白名单校验)
- Agent 工具:src/tools.ts —— 依赖注入避免循环依赖;安装守卫 src/guard.ts 拒绝裸 dsh plugin/npm/yarn/bun/pnpm 变更命令(只读 verb 放行),拒绝原因直接指路 plugin_ 工具与 dshpm;systemPrompt.section 常驻提示同一条规则
- 模糊打分:src/rank.ts —— vendor 自官方 0.1.3-alpha.1 rankByName 的有序子序列对齐算法(长仓库名适配版),client/host 共用纯函数
- CLI:src/cli.ts —— 复用受保护链路(ctx 可空:无宿主进程时跳过 live 应用,文件级操作与 Web UI 完全一致)
- Client:src/client/ —— 注册 settings.plugins.tab(遮蔽官方只读列表 + manager)+ settings.section(marketplace/kinds);同源 fetch 调 REST;危险操作行内二次确认(无弹窗)
- 通信:官方 webServer 路由 + 同源 fetch(不走 Typert Remote)
已知限制
- 禁用被依赖的条目可能导致 profile 启动失败(官方 fail-loud 设计);恢复:手动删除该 profile cordis.patch.yml 里的 managed 块
- 安装来自 git 的 bundle 需在终端放行 pnpm allowBuilds(命令输出会回显)
- 随机行(无显式 id 的挂载行)不可经此启停(id 每次挂载变化)
- git 子包安装:多包仓库用 #路径:<dir> 约定指定子目录(子目录必须位于克隆缓存内)
- 质量门可能误伤未声明运行时依赖的插件(保守策略,可加白名单);Node 内置模块说明符(裸名 crypto 与 node:crypto 等价)已豁免——由运行时不依赖提供,不构成缺失依赖
- 官方包默认只能 peerDependencies(普通依赖会装出第二份拷贝并劫持官方 loader 行);豁免白名单:@deepseek-ai/schemastery(官方 cookbook 允许的运行时 validator,模块身份不敏感,官方 dsh-llm-deepseek 即按 dependencies 声明)
- 安装守卫只拦 agent 工具调用,拦不住用户在终端手工执行裸 dsh plugin
- 更新检测边界:本地目录安装(非 git)报告"不可检测";git URL 源需 manifest 记录安装 commit(gitHead);用户本地 git 工作区只做只读比较(不 fetch),离线时报告"无法判定"而非"无更新"
- 健康检查为静态+尽力而为:同名注册冲突依赖源码正则扫描(动态拼接的名字检测不到);语义冲突无同名可查,不在检测范围
- 手动安装的插件不会自动挂载:管理器显示"未挂载"并提供"挂载"/dshpm mount,不擅自改变 profile 行为
- 环境变量过滤只覆盖常见敏感键形态(TOKEN/KEY/SECRET/PASSWORD/PASS/CREDENTIAL);宿主其它未匹配形态的变量仍会进入 git 源安装的 pnpm 解析进程(安装为 link 语义,不执行第三方脚本)
- 市场条目来源于 awesome 目录,个别仓库可能已删除/私有
- 市场代理:host 读 HTTP_PROXY/HTTPS_PROXY;系统代理/规则模式加速器对 Node 进程无效(undici 不读系统代理)——把代理写进环境变量或改 TUN/全局模式
- 市场索引项目(DSH-Plugins-Marketplace)为第三方维护:索引数据问题由精选覆盖层与安装质量门兜底;GitHub API 未认证限流 60/h 仅影响 catalog 独有条目的星数富化(量小,列表不受影响)
- REST 信任围栏仅按浏览器语义放行:非 HTTP 载体(第三方桌面壳/自定义协议分发 app:// 请求)不带 Host 头时,请求须无 Origin 且非 cross-site 才被接受(本地非浏览器发起者语义,issue #11);这类载体若连请求体都不转发(无 stream 也无 body),操作会以字段校验错误返回而不是崩溃
- 删除 bundle 插件的实时卸载失败/超时不再静默:输出会提示「live unmount 未完成,请重启后完全生效」——半挂的 loader 树可能让 Web 服务已关但进程不退,此时自动重启链路可能卡死,需手动拉起(issue #10;根治在宿主侧 flush 超时兜底)
- nvm 用户注意:子进程命令按"运行中 node 目录 → PATH → $NVM_DIR"兜底并注入 PATH——宿主进程不在 nvm 激活的 shell 中启动也能工作;仅当 dsh 未安装时才需从 nvm 激活终端
开发
pnpm run build # host: tsc(标准装饰器转译); client: tsdownbash
pnpm test # 纯函数单测(node --test 跑 dist 产物)
教训记录:host 必须用 tsc 构建(tsdown/rolldown 会保留原生装饰器语法,Node 不支持);插件不能同时导出 default(类)与 named(apply)——Loader 会丢弃 apply。新增 host 源文件需加入 tsconfig.host.json 的 include。
客户端产物的 externals 必须对齐官方平台种子表
tsdown.client.config.ts 里的 PLATFORM 是唯一允许 external 的清单,须与官方 packages/client/web/src/platform.ts 的 PLATFORM_MODULES 逐项一致:
- external 了表外的包 → 浏览器抛 require("x") missed the module table,整个插件页面启动中断(所有插件的 UI 一起消失,不只是出错的那个)
- 内联了表内的包 → 模块身份分裂(同一个包两份实例,服务/上下文对不上)
这张表随 DSH 版本变动:0.1.2-alpha.1 删除了 @deepseek-ai/dsh-client-runtime、新增了 @deepseek-ai/dsh-client-store。每次跟进 DSH 版本都要比对该表并重新构建产物。
tests/client-boot.test.mjs 把这条契约做成了自动化检查:它用模拟的模块表真正启动 dist/client.js,越表 require 会当场抛 missed the module table;同时核对五个页签的 slot 注册面、zh/en 字典键位对齐与可渲染性。
相关
- 源码与 Issue:github.com/LX2000WASD/dsh-web-plugin-manager
- 市场索引数据:DSH-Plugins-Marketplace(topic:dsh-plugin 全量索引,第三方维护)
- 精选数据:awesome-dsh-plugins
- dsh.so 验证与安全数据:dsh.so(插件独立验证 L1–L5 与自动化安全扫描,第三方维护;仅叠加徽标,不作安装源)
- 许可证:MIT预览分享卡片长什么样 ↓
分享 · DeepSeek Harness Hub
LX2000WASD/dsh-web-plugin-manager
在 Web UI 中一键管理 DeepSeek Harness DSH…
相关插件 · 市场 / 管理
插件精选列表awesome-dsh-plugin/awesome-dsh-plugin
DeepSeek Harness(dsh)插件精选列表
★ 22,025未汉化DSH
73.6
新词雷达zhuyansen/new-term-radar
new-term-radar新词雷达
★ 2,049已汉化MCP
72.8
插件生态导航0xsline/awesome-deepseek-harness
安装 贡献指南 DeepSeek Docs 公开插件目录 Issues
★ 1,188已汉化DSH
72.6
技能注册中心iflytek/skillhub
企业级开源智能体技能注册中心——发布、发现并管理组织内可复用的技能包。
★ 5,358已汉化DSH
71.9
插件市场目录imsai-sh/awesome-deepseek-harness-plugins
面向 DeepSeek Harnessdsh生态的社区插件目录,共收录 13938 个插件含 PR 收录与…
★ 350已汉化DSH
71.8
插件精选清单Anil-matcha/awesome-dsh-plugin
DeepSeek Harnessdsh精选指南——DeepSeek…
★ 1,013已汉化DSH
71.7