dsh-web-plugin-manager 功能参考(工作区文档)

August 27, 2026 · View on GitHub

README 只保留功能速览;本文件存放功能与限制的细致说明,供开发与排障查阅。

查看

  • 四源合并:include 树稳定行(EntryOptions.id,官方语义稳定,Loader 挂载 id 每次随机,patch 定位必须用它)+ dsh.profile.bundles 层栈 + package.json 依赖 + cordis.patch.yml insert 行
  • 非运行 profile 显示离线合成条目(官方 in-box 包 base/web-app/headless 不标记为已安装)
  • 已安装但未挂载的依赖:官方 CLI/pnpm 手动安装的非 bundle 插件只写依赖、不写挂载行(从未被加载)——list() 合成为 unmounted 条目,目录页显示「未挂载」标签与「挂载」按钮,一键补写 managed insert 行(运行中 profile 实时生效)

实时启停

  • 受控编辑 profile 的 cordis.patch.yml# dsh-plugin-manager:managed:start/end 标记块,行级操作、原子写入(tmp + rename),保留用户内容,可逆可审阅
  • 变更先经 loader include 条目直接应用(entry.update,与平台 watchUserPatches 同通道)再写文件——绕开平台 watcher 死锁(HMR 卸载等待自身 disposables 循环等待),实时生效、零重启
  • 插件自有的 patch 文件 watcher:手动编辑持续实时生效,不依赖 HMR 生命周期
  • 5s 超时兜底;跨 profile 隔离(仅作用于运行中宿主 profile)
  • 卸载(删除插件)的实时卸载失败/超时不再静默liveUnmountPackageapplyLiveOps{ok, message} 透传给删除结果——失败时输出附「live unmount did not complete,请重启后完全生效」,且结果 live: false。半挂的 loader 树(Web 服务已关但进程不退)会让自动重启链路(sessions.flush 永不 settle → scheduleExit 不执行 → 进程退出兜底不触发)卡死,此时需手动拉起进程;根治在宿主侧 flush 超时兜底(issue #10)
  • 随机行(无显式 id 的挂载行)不可经此启停——id 每次挂载变化

安装

  • 调用官方 dsh plugin CLI(pnpm reconcile),保护 in-box bundles(base/web-app/headless 从 dsh.profile.bundles 恢复)
  • 非 bundle 插件自动写 managed insert 行并实时挂载;bundle 插件进入层栈,重启后加载
  • git 源自动 clone 进 $DSH_HOME/plugin-manager-src 缓存;已发布 npm 的包优先走 npm 安装(npm-first)
  • 安装即质量门(见下),任何问题自动回滚,profile 保持可启动
  • 行 id 冲突预检:slugify 后的行 id 与既有行(insert 行同名不同包、用户顶层行)冲突时拒绝安装并回滚——避免 loader 拒绝整棵树

质量门(安装时)

  • 扫描整条加载链(入口 + 相对 import 可达文件,BFS 有界)的 import,对照声明依赖 + 平台白名单
  • 白名单:@deepseek-ai/dsh-client-* / cordis-plugin-* 前缀族 + cordis / react 等 loader 提供项
  • 覆盖副作用导入、re-export、动态 import、minified 形态;import type 不误报
  • 声明了但未安装的依赖同样拦截;bundle 插件 cordis.patch.yml 行名逐一校验
  • 官方包只能 peerDependencies:普通 dependencies 命中安装兜底闭包的 @deepseek-ai/* 一律拦截回滚——普通依赖会在 profile 内装出第二份官方拷贝,loader 最近优先解析劫持官方行,模块身份(unique symbol/class)分裂导致 Cannot read properties of undefined (reading 'prepare') 类运行时故障;写 peerDependencies 即可(autoInstallPeers: false 下经共享兜底目录解析,全 profile 共享一份)。豁免名单 OFFICIAL_DEP_ALLOWED(analyze.ts):@deepseek-ai/schemastery——官方 cookbook 允许的运行时 validator,ValidationErrorSymbol.for 全局符号 duck-typed、schema 为纯函数闭包,双拷贝不分裂身份(官方 @deepseek-ai/dsh-llm-deepseek 即按 dependencies 声明)

更新

  • npm 包对比 registry dist-tag latest;git 缓存源(link: 路径是 git 仓库)git fetch 对比远端 HEAD(@{u} 回退 FETCH_HEAD);git URL 源 ls-remote 对比安装 commit(gitHead)
  • 可更新卡片淡绿边框,更新按钮在删除左侧(仅检测到更新可点),详情显示当前/最新版本
  • 更新动作:npm @latest 重装 / git 缓存 fetch+reset 后重跑官方 add / git URL 源重解析,均带质量门与回滚
  • 边界:本地目录安装(非 git)无上游可比,报告"不可检测";git URL 源需要 manifest 记录安装 commit(gitHead)

健康检查(src/analyze.ts,离线引擎)

  • 依赖图:全包 entry imports → providers(含 pnpm symlink、exports 子路径)→ 包间依赖边;拓扑排序输出(加载顺序提示,cordis 激活本身是服务可用性驱动)
  • 缺失依赖(imports 无提供者且未声明)、被禁用依赖、循环依赖(DFS)、重复 patch 行 id
  • 同名注册冲突(确定性 fail-loud 故障):服务名(new Service/ctx.provide)、工具名(ctx.tools.register)、prompt section 名(ctx.systemPrompt.section)、web 路由路径(ctx.webServer.register)——源码正则扫描,动态注册(字符串拼接的名字)检测不到
  • peerDependencies 版本满足性(简化 semver:^/~/>=/<=/>/</精确/星号),含经共享 fallback 解析的官方核心包
  • 官方包重复安装(official-duplicate):@deepseek-ai/* 同时存在于 profile node_modules 与安装兜底目录 → 报肇事插件(哪个依赖引入的)与修复指引
  • 运行时诊断(宿主):pending 注入根因(静态 inject 声明 vs 活跃服务表)、fiber 加载失败原因(_error
  • 作用效果冲突(语义冲突)不在静态检测范围:两个插件做相反的事(互相矛盾的 prompt 指导、竞争同一事件流)没有同名可查,只能靠运行时观察尽力而为

分级自动修复(管理页健康检查区)

  • A 级(安全默认,一键自动修 / 单条「修复」)duplicate-row-id(保留第一个删其余重复顶层行)、disabled-dependency(恢复启用被依赖条目)、official-duplicate(删除 profile node_modules 里的重复官方包拷贝 + 清理 manifest 声明)
  • B 级(建议 + 行内确认,不弹窗)service/tool/section/route-conflict——建议"禁用后注册者",按钮两态(执行建议 → 确认执行?)
  • C 级(只输出)missing-import / circular-dependency / peer-mismatch / 运行时诊断
  • 交互:行内按钮 + 状态反馈(已修复 ✓),执行细节进命令输出区;fixAll 批量跑 A 级;修复后自动重新分析
  • 服务端:fixIssue(profile, action, target) / fixAll(profile)(mutation mutex 串行,A 级动作执行前不二次确认)

环境管理

  • 设置 → 插件 → 环境:启动/停止(终端或后台)、复制/转移插件(按记录源重装)、创建/重命名/删除 profile
  • 官方 profile(web/headless)只读,不可管理

市场

  • 静态索引为主DSH-Plugins-Marketplace 的 CI 索引 registry.json.gz(topic:dsh-plugin 全量 ~3100 仓库,含星数/更新时间/pkg_name/版本/分类,每 2 小时更新)——多源兜底链:api.github.com contents(有 GH_TOKEN/GITHUB_TOKEN 带认证)→ jsDelivr CDN(generated_at 新鲜度 6h 校验)→ raw.githubusercontent → 本地磁盘缓存(上次成功完整索引)→ GitHub 搜索 API(topic:dsh-plugin,残缺应急,不落盘);索引条目自带星数,不再逐个打 GitHub API
  • 精选覆盖层awesome-dsh-plugins 结构化 catalog(catalog/plugins/*.json + tombstones)+ PLUGINS.md 双源合并,叠加在索引之上(状态徽标/精选描述/包名/分类),精选独有条目追加;24h 缓存,空结果不写正缓存
  • dsh.so 验证/安全叠加dsh.so 独立索引(1630 条,全部带 verification L1–L5 + 自动化安全扫描)按仓库名叠加 verification/security 徽标到卡片(TAG 行与 awesome 状态徽标同行,不新增条目、不作安装源);dsh.so 索引独立磁盘缓存 24h,失败降级不阻塞;注意扫描为静态启发式,high 风险可能是误报(如本管理器自身也被标 high)——徽标是"装前留意"信号,最终判定仍是安装质量门
  • 已安装判定在服务端(每请求按目标 profile 计算,12 并发池标注):① npm 包名(registry pkg_name / 仓库名)② manifest repository 双向匹配(同名不同仓库不误判)③ git 缓存源 owner-repo 身份 ④ ~/.dsh/skills|.agent-presets 目录探测;返回 installed / installedVersion / latestVersion(索引版本字段)/ updateAvailable(仅严格更高才提示,回滚不误报)
  • 同名包冲突消解:同一 pkg_name 只保留一条(已安装优先、否则星数高者),dropped 计数透传前端提示「N 个同名包已隐藏」
  • 卡片动作:未安装 → 安装;已装无新版 → 绿色「已安装 vX」;已装有新版 → 橙色「更新」(npm 包走受保护 update 链路重写 specifier + 质量门 + 回滚,git-only 源重装);星数排序时已安装置顶
  • 卡片布局:标题省略号不挤占按钮区;短 meta 行(星数缩写 2.5K / 来源 npm包|git仓库 / 类型占位);TAG 行(审核状态 + 功能分类本地化);单/双列切换(localStorage 记忆);增量渲染——首屏 120 条 + 触底加载更多 + content-visibility: auto(~3000 条列表不卡顿,无需服务端分页)
  • 缓存:进程内存镜像(listing 与 profile 无关,切 profile 只重算已安装标记,零磁盘 IO)+ 磁盘 24h 缓存 + 失败负缓存 5min + registry 原始索引缓存;refresh=1 是唯一强制网络路径
  • 网络健壮性:每请求 15s 超时(AbortSignal.timeout);支持 HTTP_PROXY/HTTPS_PROXY/NO_PROXY(undici ProxyAgent——Node 全局 fetch 会丢弃 dispatcher 选项,市场请求必须走 undici 自身 fetch);失败原因负缓存 5 分钟(marketplace-failure.json,避免每次进页重跑全量 GitHub 往返);索引全挂且无缓存时空列表直接显示失败原因
  • GitHub API 未认证限流 60/h:仅 catalog 独有条目的星数富化会打 API(量小);403/429 停止富化(星数降级用上次快照元数据),列表本身不受影响;raw 兜底源可达时列表保持非空
  • 系统代理/规则模式加速器对 Node 进程无效(undici 不读系统代理)——市场为空且此类加速器用户,把代理地址写进环境变量,或改 TUN/全局模式

agent 工具与安装守卫

  • plugin_status / plugin_install / plugin_uninstall / plugin_toggle(目标 profile 由配置 profile 指定,默认 web);依赖注入避免循环导入
  • plugin_search:自然语言检索市场(本地索引,name 3 分 / topics 2 分 / 描述 1 分加权,空查询按星数;中英文分词),结果带已安装标记与仓库链接,并提示"安装前先浏览仓库"(索引元数据无法判断质量,安装本身走质量门)
  • 安装守卫(src/guard.ts):tools.guard 拒绝 bash/run_code 中裸 dsh plugin add/remove/update 与指向 profile 目录的 pnpm add/remove(只读 verb list/status/dump-config/help 放行),拒绝原因直接给模型指路 plugin_* 工具与 dshpmsystemPrompt.section(order 300)常驻提示同一规则
  • 守卫只拦 agent 工具调用,拦不住用户在终端手工执行裸命令

多类型安装(skill / agent 预设)

  • 类型检测分层:根/嵌套 agent.cordis.yml → 预设(官方单文件判定,preset.yml 仅显示元数据);根 package.json 声明 DSH 能力 → cordis 插件;根 SKILL.md → skill;嵌套预设/插件/技能根;其余(含含 install.sh 的仓库——永不自动执行第三方脚本)→ 非三类,拒绝安装并加入市场屏蔽名单
  • skill 安装到官方根 <dshHome>/skills(frontmatter name 优先,技能集合仓库逐个装,跳过点目录/node_modules/vendored);chokidar watch 默认开启 → 安装/删除即热加载;预设安装到 <dshHome>/.agent-presets(目录名即 preset id,官方每次会话发现重读);预设的管理(复制/删除/默认)由官方设置页完成
  • 安装记录 installed-kinds.json(市场安装的来源/类型/位置/时间,串行队列读写);市场已安装判定与卡片类型徽标(skill/预设/插件)来自记录
  • 市场卸载:管理页「技能与预设」区块(记录列表 + 卸载 + git 源重新拉取);dshpm uninstall-kind <owner/repo>;skill/预设删目录含路径越界防护,cordis 按记录逐个走受保护 remove
  • 屏蔽名单:安装时检测为非三类的仓库写入 blocked-repos.json,市场所有列表路径(缓存/新拉/兜底)统一过滤,响应带 blocked/blockedRepos;市场页提供「解除屏蔽」

插件拥有的 Agent 预设(归属管理)

  • 背景:预设目录名是 preset id,不是所属插件——插件(如 dsh-agent-rp)自带的伴随预设是插件自己复制进 <dshHome>/.agent-presets 的,卸载插件后预设成为孤儿(选择器残留、选中报错)。归属标记回答「这个目录是谁的、还是原版吗」。

  • 标准标记(本管理器安装预设时写入):.dsh-preset-owner.json = {format: 0, owners: [包名], digest: sha256(agent.cordis.yml[+preset.yml])},不覆盖已有标记。

  • 兼容读取(生态既有惯例,只读不写):gamelike-plugin-manage 的 .plugin-manage-owner.json(owners 数组)、dsh-agent-rp 的 .dsh-agent-rp-owner.json(owner 字符串 + format + digest,严格校验 format)。标准标记损坏 → 视为无归属(不删);兼容标记损坏/format 不符 → 单个跳过。

  • 卸载清理(remove / uninstall-kind / plugin_uninstall):只删「唯一 owner == 被卸载插件 且 digest 匹配(用户未修改)」的预设;用户改过的跳过并报告;多 owner 预设(归属重叠)不处理;system 信任预设绝不碰;删除优先走宿主 agentPresets.remove()(处理 settings.default 与 standing 会话),宿主不可用(CLI)降级直删(rmRetry + 路径守卫)。预设是全局的:其他 profile 仍安装该插件时不清理

  • 禁用归档 / 启用恢复(setEnabled):禁用插件把其拥有的预设目录移出 user root(<dshHome>/plugin-manager-cache/preset-archive/,含用户修改版,零数据损失,选择器即时消失);重新启用自动移回(同名冲突保留新预设并提示)。禁用从不删除——宿主 broken 判定只查 YAML 语法、不反映「引用的插件被禁用」,且临时禁用不应毁数据。

  • 已知限制:CLI 直删路径不清宿主 settings.default(无 ctx);skill 无此问题(SKILL.md 不引用插件代码,残留无害)。

  • 类型检测分层:根/嵌套 agent.cordis.yml → 预设(官方单文件判定,preset.yml 仅显示元数据);根 package.json 声明 DSH 能力 → cordis 插件;根 SKILL.md → skill;嵌套预设/插件/技能根;其余(含含 install.sh 的仓库——永不自动执行第三方脚本)→ 非三类,拒绝安装并加入市场屏蔽名单

  • skill 安装到官方根 <dshHome>/skills(frontmatter name 优先,技能集合仓库逐个装,跳过点目录/node_modules/vendored);chokidar watch 默认开启 → 安装/删除即热加载;预设安装到 <dshHome>/.agent-presets(目录名即 preset id,官方每次会话发现重读);预设的管理(复制/删除/默认)由官方设置页完成

  • 安装记录 installed-kinds.json(市场安装的来源/类型/位置/时间,串行队列读写);市场已安装判定与卡片类型徽标(skill/预设/插件)来自记录

  • 市场卸载:管理页「技能与预设」区块(记录列表 + 卸载 + git 源重新拉取);dshpm uninstall-kind <owner/repo>;skill/预设删目录含路径越界防护,cordis 按记录逐个走受保护 remove

  • 屏蔽名单:安装时检测为非三类的仓库写入 blocked-repos.json,市场所有列表路径(缓存/新拉/兜底)统一过滤,响应带 blocked/blockedRepos;市场页提供「解除屏蔽」

CLI(dshpm)

  • bin 随插件安装进入 profile 的 node_modules;也可 node <profile>/node_modules/dsh-web-plugin-manager/dist/cli.js 直接调用;--home 指定 DSH_HOME
  • 所有变更走与 Web UI 完全相同的受保护链路:pnpm add/remove(官方 CLI)→ 质量门 → 自动回滚 → 安装后分析 → insert 行维护;ctx 可空:无宿主进程时跳过 live 应用,文件级操作与 Web UI 一致
  • 命令:
    • dshpm install <source>:npm 名 / github:user/repo / git URL / tarball / 本地路径
    • dshpm remove <name>:insert 行 + 包依赖一并清理(含 node_modules)
    • dshpm mount <name>:补挂载官方 CLI 手动安装的未挂载依赖
    • dshpm list:bundle 层栈 / 已装包 / insert 行
    • dshpm analyze:健康检查全文输出,有问题退出码 1

架构模块

  • Host:src/index.ts —— PluginManagerServicectx.pluginManager)+ /api2/plugin-manager/* REST(webServer.register
  • 实时应用:src/live.ts;分析引擎:src/analyze.ts(与质量门共享扫描器,永不漂移);Patch 编辑:src/patch.ts(YAML 陷阱:@ 包名引号、空数组文档 []、纯注释文件恢复模板);网络助手:src/net.ts(超时 + 代理);REST 原语:src/rest.ts(信任围栏 + 请求体读取,纯函数可单测);Agent 工具:src/tools.ts;守卫与提示:src/guard.ts;CLI:src/cli.ts
  • Client:src/client/ —— settings.plugins.tab(all 遮蔽官方只读列表 + manager + environments)+ settings.section(marketplace);同源 fetch 调 REST(不走 Typert Remote)

已知限制明细

  • 禁用被依赖的条目可能导致 profile 启动失败(官方 fail-loud 设计);恢复:手动编辑该 profile 的 cordis.patch.yml 删除 managed 块
  • 安装来自 git 的 bundle 需要用户在终端放行 pnpm allowBuilds(命令输出会回显)
  • git 子包安装:多包仓库用 #路径:<dir> 约定指定子目录(#ref 是 git ref)
  • 质量门可能误伤:未声明运行时依赖的插件会被拦截回滚(保守策略);若插件确实由 Loader/host 提供该模块,需在 manifest 声明或加入白名单;Node 内置模块说明符(裸名与 node: 前缀等价)已由 isBuiltin 统一豁免——运行时无条件提供,不构成缺失依赖(issue #9)
  • 市场条目来源于静态索引 + awesome 目录,个别仓库可能已删除/私有(安装时报 Repository not found);索引项目本身是第三方维护(社区项目),其数据问题(打错 tag、非插件仓库)由精选覆盖层与安装质量门兜底
  • nvm 用户注意:子进程命令(dsh/npm/pnpm/git)解析按「运行中 node 目录 → PATH → $NVM_DIR」兜底,并把命中的工具目录注入子进程与终端窗口的 PATH——宿主进程不在 nvm 激活的 shell 中启动(桌面启动器/服务/nohup)也能工作;仅当 dsh 完全未安装时才需要从 nvm 激活的终端启动