← 返回列表
未验证
在 dsh Web 界面里显示当前 Harness 版本和每个已挂载插件的包版本。
尚未跑自动兼容性验证,可查看页面内的依赖与入口分析。 · 最近上游提交 2026/9/6 · 已提供中文文档
在 dsh Web UI 中显示正在运行的 DeepSeek Harness 版本以及每个已挂载插件的包版本。
综合分
28.4
GitHub 分
28.4
用户评分
—
★ Stars
0
周下载量
—
安装插件(需先安装 dsh CLI 引擎:npm install -g @deepseek-ai/dsh)
dsh plugin --profile web add kristol07/dsh-version-inventory该插件未发布到 npm,走 GitHub 源安装(pnpm 若拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑)
信任档位:仅索引本站尚未对其实装验证,仅收录元数据
- 是什么
- dsh 原生插件 · ui
- 装得上吗
- 本站尚未做安装检查
- 安全吗
- 本站尚未对该插件做风险分级(暂未覆盖,不等同于无风险)
- 还在维护吗
- 活跃:最近一次提交在 19 天前
档位由下列信号合成:本站实装验证(真实安装,当前最高到 L4)· 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。下方各区块是它的证据明细。 验证判据与等级说明 →
数据截至 2026/9/21(元数据每日更新 · 实装验证按队列轮转,单条结论的验证时间见上方)
依赖的 DSH / Cordis 模块
@deepseek-ai/cordis@deepseek-ai/cordis-plugin-loader@deepseek-ai/dsh-agent-presets@deepseek-ai/dsh-client-locale@deepseek-ai/dsh-client-modules@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-tools用户评分
还没有人投票,来当第一个
订阅周报,不错过优质插件更新
每周一封 · 高评分插件 + 新用户活动
README
由 DeepSeek 最新模型翻译生成dsh-version-inventory
在 dsh Web 界面里显示当前 Harness 版本和每个已挂载插件的包版本。
设置 → 插件 → 版本 标签页,就在官方的「插件列表」标签旁边。官方那个标签只显示模块名、启用状态和 fiber 状态,不显示版本号;这个插件补上版本这一列,并额外给出安装位置、Node 版本、DSH_HOME、重复副本告警,以及官方包与当前 Harness 版本不一致时的提示。每一行都能复制成 name@version,表头则复制一份可安全外发的整体环境报告。同一份读数还注册成了 dsh_version_inventory tool,会话里的 agent 可以直接读取。
界面跟随客户端的语言设置,内置英文和简体中文。
两个平面
Harness 把插件挂在两个平面上,面板两边都读:
- 全局平面 —— profile 的 Loader 树:bundle patch 层叠加上你自己的 cordis.patch.yml。ctx.loader.entries() 走的就是这里。
- preset 平面 —— 每个 agent preset 的 composition(agent..yml),按会话挂载:某个会话选了哪个 preset,才挂那个 preset 的行。所以一个只存在于 preset 里的插件(比如创造模式的 @deepseek-ai/dsh-tool-cordis)在全局 Loader 树里根本查不到。
只读全局平面会漏掉后者。面板通过可选服务 ctx.get('agentPresets') 的 compositionInventory() 补上 —— 那是读文件,不是读活的树,所以没开会话也能列出来,而且读取本身不会提前挂载任何 preset。没有 preset 名册的部署是一种真实形态,不是故障,这种情况下 preset 平面为空。
每个挂载都带自己的平面标记,面板顶部可以按平面过滤;同一个包在两个平面各挂一次,会合并成一行、列出两个挂载。
「包」和「挂载」的区别
包是代码,挂载是这份代码的一次装载:一个 id、一份 config、一个 fiber。同一个包被挂多次是设计意图,不是重复项 —— 官方 @deepseek-ai/dsh-tool-subagent 在创造模式里就被挂了三次,靠 config 区分成 subagent / subagent_fork / subagent_codex 三个工具。
所以每个挂载都会显示自己的 config 摘要,这是唯一能把两次挂载分开的东西:
● tool-subagent 创造模式 运行中
provider=spawn toolName=subagent backgroundMode=continuable
● tool-subagent-fork 创造模式 运行中
provider=fork toolName=subagent_fork
摘要不是 config 的转储:只取顶层字段,嵌套值折叠成形状({provider, toolName, …}、[3]),长字符串截断到 60 字符,超过 8 个字段的部分只计数。键名带 key / token / secret / password / credential / auth 的字段一律只显示 * —— 宁可多挡,漏一个 token 的代价是轮换密钥,多挡一个无害字段的代价只是去看一眼配置文件。
preset composition 的清单本身不携带 config,所以 preset 行显示「config 不可见」,而不是显示成「无 config」。
它怎么拿到版本号
两个平面给出的都只是模块说明符(@deepseek-ai/dsh-tool-bash、file:///…/lib/index.js),不记录版本。所以宿主半边做三件事:
1. 遍历全局条目(跳过 group)和每个 preset 的 composition 行;
2. 用 Loader 自己的解析器(loader.internal.resolveSync,退化时用 createRequire(baseUrl).resolve)把说明符解析成模块 URL,再从该文件向上找最近的 package.json —— 刻意绕开 exports 映射,因为不是每个包都导出 ./package.json;
3. 读出 name / version / description / dsh.bundle / dsh.client,并按包所在目录把多个挂载合并(子路径条目 @deepseek-ai/dsh-tool-subagent/model-selection-settings 会归到 @deepseek-ai/dsh-tool-subagent 名下,因为两者解析到同一个目录)。
按目录而不是按包名归并是有意的:同一个包同时存在两份不同版本是最该被发现的情况 —— Cordis 服务、品牌类型和 instanceof 全都按运行时身份匹配,两份副本会静默失配 —— 而按包名归并恰好会把第二份折叠掉、连版本都丢了。现在两份副本各占一行、并排排序,各自带「副本」标记,顶部还有一条提示点名。
解析基址的顺序不是随意的:preset 行的包名要用 profile 的基址解析,preset 自己的目录只作为相对路径的兜底。这跟名册自己的做法一致 —— 用户自建的 preset 放在 harness home 下,Node 向上找 node_modules 永远走不到 harness 的依赖,所以 preset 目录是解析包名的错误基址。路径型说明符还会先 existsSync 校验,否则一个不存在的相对路径会向上撞到某个无关的 package.json 并被当成答案。
Harness 自身的版本来自 process.argv[1] 向上找到的 @deepseek-ai/dsh 的 package.json —— 也就是正在运行的那个 bin,无论它是源码检出里的 apps/cli/src/bin.ts 还是安装出来的 node_modules/@deepseek-ai/dsh/lib/bin.js。找不到时退化为「已加载的 @deepseek-ai/ 包里出现次数最多的版本」,并在界面上标明这是推断值,不会假装是确定答案。
cordis: 内置条目没有包也没有版本,界面如实显示为「Cordis 内置」而不是编一个版本出来。
复制版本信息
两个控件,对应两个会被问到的问题:这一个插件我用的是哪个版本,以及这套安装整体长什么样。
- 每一行包都可以复制成 name@version —— 一个整体,粘到消息里、终端里、搜索框里都一样能用。
- 表头复制一份简短的环境报告:Harness 版本及这个答案的来源、Node 与平台、UTC 采集时间、每个第三方插件的版本与所在平面、官方包(版本一致时折成一行)、内置数量,以及采集过程中的告警。
报告永远是整份清单,不是当前筛选出来的视图。搜索框和平面下拉决定的是某一个人此刻在看什么;如果报告继承了它们,就会在不做任何说明的情况下少报一套安装。
报告里没有文件系统路径、没有 DSH_HOME、没有用户自己写的 preset 名称、也没有任何 config 值。 这是格式本身,不是一个开关。读取路由之所以被围在同源 loopback 里,正是因为快照会点名宿主路径和 config 键;而复制控件存在的意义就是把文本带离这台机器,恰好把那道围栏反了过来。所以报告只用快照里可以交给别人的那一半。唯一的例外是重复副本:几份副本如果没有区分依据就没有意义,那里会去掉最长公共目录前缀,只保留真正不同的片段(…/node_modules/dup 对 …/plugins/a/node_modules/dup)。
面板里的每一处保留说法都会跟着一起复制。推断出来的 Harness 版本仍然带着「由已加载包推断」,没有清单版本的包写成 name@unknown,采集告警原样带上 —— 复制时丢掉的限定词,正是把这个面板的推断变成读者眼里事实的那一步。
正文是纯文本,一行一条事实、以 - 开头:在聊天窗口或文件里直接读就是这个样子,被当作 Markdown 渲染时也不会丢掉换行。语言跟随面板,写入走 Harness 自己的 writeClipboard —— 在没有异步 Clipboard API 的非安全上下文里回退到 execCommand,被拒绝时返回失败而不是抛错。和 dsh 所有复制控件一样,被拒绝时保持沉默:按钮文案不会变。
在会话里让 AI 直接读
同一份读数还注册成了一个模型可见的 tool:dsh_version_inventory。dsh 会话里的 agent 可以自己回答版本问题,而不必让用户去打开设置页、复制一份报告再粘回来。这恰恰是最需要它的读者 —— 「这个插件跑的是哪个版本」通常出现在排查过程中间,它是下一步的前提,而不是对话本身的目的。
一个可选参数 package,接受包名的大小写不敏感子串。不传就报告整套安装;传了就只报告匹配的包。对最常见的那个问题来说,这是二十五行和五行的区别。
它返回的就是复制控件用的同一份投影,这是刻意的,不是省事。tool 的结果不是一次私下的读取:它会进入对话记录,在之后每一轮都发给模型提供方,还可能被导出 —— 暴露面和一份被粘贴出去的报告相同,正好和读取路由所围栏的那个方向相反。共用同一份投影,就意味着模型这个读者不可能悄悄变得比人类读者更宽。它同样会丢掉每个包的 description,而且这里比在面板里更要紧:description 是第三方包作者写的文本,而这是唯一一条把它喂给模型的路径。
canonical value 是结构化的,句子留给 renderer —— 这也是 tool 编写契约要求的形状:PTC 调用方读字段,而不是去解析散文;native 调用看到的则是和人复制出来的同一份报告。告警在那里同样保持结构化,遵循宿主半边一贯的那条规则:事实过线,句子由读者来写。
渲染出来的文本一律是英文,与面板语言无关。Harness 的 locale 是浏览器侧的偏好,宿主没有 locale 服务,所以 tool 直接按 en 渲染 —— 它是词典键的真相来源,也是 Harness 自己的回退语言 —— 而不是去猜一个没人声明过语言的读者。
tool 这条路径在运行时不 import tools 包。ToolDefinition 是类型,ctx.tools.register 收的是普通对象,defineTool 只是同一形状之上带类型的构造器 —— 所以 @deepseek-ai/dsh-tools 仍然只是 devDependency,本包也保住了「所有能力都经由 ctx 到达」这条性质。代价是 raw definition 要自己校验参数,对一个可选字符串来说,这比多一个依赖便宜。
结构
| 文件 | 作用 |
|---|---|
| src/index.ts | 宿主插件入口;inject: ['loader'],路由和 tool 各自在嵌套注入下注册 |
| src/inventory.ts | 采集逻辑:两个平面的挂载 → 包清单 |
| src/web.ts | GET /dsh-version-inventory/api/list,同源 + loopback + 自定义头的信任围栏 |
| src/client/index.tsx | 通过 ctx.slots.inject('settings.plugins.tab', …) 贡献标签页,并注册词典 |
| src/client/panel.tsx | 面板本体 |
| src/report.ts | 可外发的投影及其渲染 —— 面板、复制控件和 tool 共用 |
| src/tool.ts | dsh_version_inventory tool:schema、canonical value 和英文渲染 |
| src/locales.ts | en / zh 词典,en 是键的真相来源 |
| src/types.ts | 两半共用的 wire 类型(浏览器侧只做 type-only import) |
宿主半边不持有任何状态:每次请求都重新读一遍。Loader 已经维护着 Entry.fiber 和 Fiber.state,preset 名册也刻意在每次调用时重读根目录,再加一份缓存只会多出一个需要同步的真相来源。
webServer 和 tools 都是嵌套依赖(ctx.inject(['webServer'], …))而不是声明依赖,所以同一个包在 Harness 跑的任何地方都能加载:headless / ACP profile 拿到 tool、没有面板;不组合任何 agent 的部署拿到面板、没有 tool;两种情况都不会为一个永远不会到来的服务把整棵树挂在 pending 上。
告警在 wire 上是结构化的事实,不是句子:宿主不知道读者用什么语言,所以它只报 { kind: 'duplicate-packages', names: [...] },句子由面板来写。路由的失败遵循同一条规则 —— 返回 { error: { kind: 'forbidden' } },而不是一句「碰巧用作者当时在敲的那门语言」写成的拒绝理由;遇到 bundle 不认识的 kind,退化成原文而不是编一句出来。这也让路由返回的 JSON 对其他读取方同样可用。
兼容性
| 项 | 要求 |
|---|---|
| Node | ^22.19.0 \|\| >=24.0.0 |
| DSH | >= 0.1.2-rc.1。面板需要 web profile(webServer + settings.plugins.tab),tool 需要 tools。只具备其中一个也是可用的安装 |
| 运行时依赖 | 无 |
这个包没有 dependencies,也没有 peerDependencies,这是刻意的。 它在运行时不 import 任何 harness 模块 —— 宿主半边只 import Node 内建模块,浏览器半边只 require 平台种子模块,其余能力全部通过 ctx. 服务获得。所有 @deepseek-ai/ 都只在 devDependencies 里,只用于类型和测试。
之所以不写成 peerDependencies:dsh 目前整个家族都在预发布版本上(0.1.2-rc.1、0.1.3-alpha.1),而 semver 的预发布匹配规则要求区间里存在同 [major.minor.patch] 的比较符,所以 >=0.1.2-rc.1 不匹配 0.1.3-alpha.1。任何写死的区间都会对正常安装报假警告。等 dsh 发出正式版本后可以再加回来。
服务缺席时的降级都是明确的:没有 webServer 就不注册路由(headless / ACP profile 照样能加载这个包,也照样注册 tool),没有 tools 就不注册 tool,没有 agentPresets 就 preset 平面为空,没有 settings.plugins.tab 声明就不贡献标签页。
构建与测试
npm install && npm test
npm test 先构建,再跑 test/ 下的六个套件:
- inventory.test.mjs —— 真实的 Cordis Loader + 真实的 package.json 解析,preset 名册用一个只实现 list() / compositionInventory() 的替身。条目一律以 disabled: true 创建:停用的条目永远不会被 import,所以测试只走解析和清单读取,不启动任何插件。同名不同版本的副本用 test/fixtures/ 下两个真实的包目录构造。
- client-bundle.test.mjs —— 用 npm 上真实的 @deepseek-ai/dsh-client-modules 扫描器跑一遍:断言本包进了 boot graph、/plugins 路由能取到构建产物、factory id 与包名一致,以及bundle 只 require 平台种子模块。最后这条是最有价值的一条 —— 浏览器插件最常见的坏法就是多出一个模块表答不上来的 require,那会在 materialize 时直接抛。
- locales.test.mjs —— 每个语言的键集合和 {placeholder} 集合都必须一致。键的一致性 TypeScript 已经保证了;漏掉一个占位符才是它看不见的。
- web.test.mjs —— 同源 loopback 围栏,以及路由拒绝时答复的形状。最后一条断言是回归守卫:路由不许自己写文案,因为在宿主进程里写下的句子,对某一类读者一定是写错了语言的。
- report.test.mjs —— 投影本身,以及它用真实 en 词典渲染出的文本。真正重要的断言是不允许出现什么:一旦某次改动开始携带路径、DSH_HOME、config 值、包的 description 或用户自己写的 preset 名称,必须先在这里失败,而不是在别人已经粘出去的消息里、或者一段模型对话记录里。
- tool.test.mjs —— 用注册表自己的 assertSupportedJsonSchema 和 validateJsonSchemaValue 检查 tool 定义。注册 raw definition(而不是 defineTool 的产物)把这两道检查从构造器上挪走了;没有它们,一个描述不了自身取值的 schema 会在挂载时或第一次调用时才失败 —— 也就是在真实会话里。它同时钉住接线方式:inject 只写 loader,两个界面都走嵌套注入。
npm run verify:pack 跑 publint 和 @arethetypeswrong/cli(--profile node16)。两处已知的例外:
- publint 会报 exports["./client"] 是「CJS 却被当作 ESM」。lib/client.js 从来不经过 Node 解析 —— 它由页面的模块系统当作 classic script 执行。harness 自己对同名文件做了完全一样的豁免(scripts/publint-all.ts 里的 isBrowserBundleFormatFalsePositive)。
- attw 的 cjs-resolves-to-esm 被显式忽略:这是个纯 ESM 包,CJS require() 不是目标。
发布前跑 npm run release:check,它把测试和打包检查一起跑一遍。打包检查刻意没放进 prepublishOnly:attw 的 --pack 会去调 npm pack,而 npm pack 会继承 npm publish --dry-run 传下来的 npm_config_dry_run,于是根本不产出 tarball —— 放在那里会让发布预演跑不起来。CI 每次推送都会跑这两项。
产物是 lib/index.js(ESM,宿主半边)和 lib/client.js(CJS,浏览器半边,带 window.__ModuleLoader__.load({ id, factory }) 外壳)。id 必须与 package.json 的 name 一致,否则浏览器模块表取不到这个 factory。
浏览器 bundle 只允许 require() 平台种子模块(react、react/jsx-runtime、react-dom、@deepseek-ai/cordis、dsh-client-store、dsh-client-ui-slots、dsh-client-ui-primitives)。其他一切都必须内联进 bundle —— 模块表答不上来的 require 在 materialize 时直接抛错。因此 @deepseek-ai/dsh-client-ui-settings / -renderer / -locale 只做 type-only 引入(拿 SlotMap、Context.slots 和 Context.locale 的声明合并),运行时不碰。
类型由 tsc 统一发到 lib/types/,两半各有一份入口声明(. → lib/types/index.d.ts,./client → lib/types/client/index.d.ts)。浏览器 bundle 不能用 tsdown 的 dts —— 它会把 __ModuleLoader__ 的 banner/footer 一起裹进 .d.cts,那是解析不了的。
安装
从 npm 安装
dsh plugin --profile web add dsh-version-inventory
package.json 声明了 dsh.bundle.patch,所以 dsh plugin 装完会自动把它加进 dsh.profile.bundles 层叠,由本包的 cordis.patch.yml 用裸包名插入条目。启动前可以先验证这一层:
dsh --profile web --dump-config
从 GitHub 安装
dsh plugin --profile web add github:kristol07/dsh-version-inventory
git 安装拿到的是源码而不是构建产物,所以 pnpm 必须运行本包的 prepare 脚本 —— 而 pnpm ≥10 在你明确允许前会拒绝。第一次 add 会失败并打印出确切的包键,把它写进 profile 的 pnpm-workspace.yaml 再重跑:
allowBuilds:
dsh-version-inventory: true
这个允许等于授权在安装时在你机器上执行本包的代码,且不在 agent 的任何沙箱内。请固定到某个 commit(github:kristol07/dsh-version-inventory#),免得之后的推送悄悄改变会执行的东西 —— 或者走 npm / tarball,那两条路都不需要构建授权。
从 tarball 安装
npm pack
dsh plugin --profile web add ./dsh-version-inventory-0.2.0.tgz
从本地检出安装(开发用)
dsh plugin --profile web add link:/path/to/dsh-version-inventory
link: 建的是符号链接,改完代码 npm run build 就能生效,不用重装。
也可以完全不安装,直接用 patch 层指向构建产物。在 ~/.dsh/profiles/web/cordis.patch.yml 里:
- insert:
- id: dsh-version-inventory
name: ../../plugins/dsh-version-inventory/lib/index.js
相对路径会以该 patch 文件所在目录为基准锚定成 file:// URL;client-modules 扫描再从这个文件向上找到本包的 package.json,读到 dsh.client 和 exports["./client"]。web profile 是 patchReload: live,改 patch 不用重启。
这几种方式不要同时用,否则会插入重复条目。
已知限制
- 报告是指纹,不是清单:它给出的是 Harness 挂载了哪些包、版本各是多少。它不是 lockfile,也无法据此重建一套环境 —— 传递依赖、patch 层的具体内容、某个会话实际组合了哪个 preset,都不在其中。两份报告一致,说明挂载的版本一致,不等于两套环境一致。
- 一次读取一份快照:面板挂载时读一次,之后靠「刷新」按钮,不订阅 Loader 变化。
- 只读:不提供启用/停用开关。
- 版本来自磁盘上的 package.json:一个热更新过、但 package.json 未随之改动的包,显示的仍是磁盘上的版本号。
- 副本检测只看解析结果:两份副本必须都被某个挂载引用才会被发现。装在磁盘上但没有任何条目引用的第二份副本,面板看不到。
- preset 行的 fiber 状态取决于是否已挂载:一个还没有会话挂载过的 preset,其行只有启用状态,没有运行状态;conditional 表示 !!js 门只有真正挂载时才能判定。
- config 摘要只到顶层:嵌套结构只显示形状,要看完整值请查 cordis.patch.yml 或 preset 的 composition 文件。
- 路由只对本地同源开放:清单会暴露宿主文件路径和 config 键名,所以 /dsh-version-inventory/api/list 要求 loopback host、同源 Origin,以及 X-DSH-Version-Inventory: 1 头。
发布
推一个 v tag 就会发布,合并到 main 不会发布任何东西。没有任何 npm secret:workflow
使用 npm Trusted Publishing,由 GitHub
Actions 通过 OIDC 换取短期发布凭据。
npm run release:check
npm version patch # 或 minor / major / 明确的 x.y.z
git push origin main --follow-tags
一次性的 GitHub / npm 配置和完整流程见 docs/publishing.zh.md,其中两个坑值得在第一次
打 tag 之前知道:仅声明 environment: npm 并不会要求人工审批;以及该环境的部署规则必须
允许 tag,只允许分支会直接卡住发布。
许可
MIT © Joel Liu