dsh-hub

DSH PLUGIN记忆与知识

仓库 HanMaXiao/dsh-hub

记忆与知识 L4 · 沙盒安装通过 安全审查:低风险 可信度:银 可安装插件
安装这个插件

dsh-hub 是 HanMaXiao 维护的 记忆与知识 类 DSH 可安装插件, 仓库为 HanMaXiao/dsh-hub,许可证 MIT。 下面按「装什么、怎么装、验没验过、跑起来要注意什么」逐项列出。

一句话选型结论

按资料字段整理

dsh-hub 是记忆与知识类的 DSH 可安装插件,验证等级 L4 · 沙盒安装通过,安全审查为低风险,有可直接复制的安装命令。验证通过不等于安全:插件会读写文件、执行命令或联网,装前先到仓库确认它做了什么;涉及密钥或系统权限的,建议先在隔离环境试装。

分类记忆与知识 收录类型可安装插件 验证等级L4 · 沙盒安装通过 安全审查低风险
验证等级 L4 L4 · 沙盒安装通过
GitHub 星标 0 复刻 0 · 未关闭 issue 0
最近更新 2026-10-30 创建于 2026-10-15
README 32.9 KB 正文体量,越大说明说明书写得越细

安装

可安装插件
在 DSH 里执行,装完用 dsh plugin list 确认已挂上;标了 L4 / L5 的命令已在沙盒里真实跑通
dsh plugin --profile web add github:HanMaXiao/dsh-hub
  1. 1
    确认版本与运行环境

    仓库没有标注兼容版本与 Node.js 要求,先确认你的 DSH 版本能装,再继续。

  2. 2
    执行安装命令

    在 DSH 里执行上面的命令;多包仓库要注意区分具体子包名,别装错包。

  3. 3
    确认已挂载

    用 dsh plugin list 查看列表里是否出现这个插件,出现即挂载成功。

  4. 4
    试跑并检查权限

    加载后跑一个最小用例,确认它能正常工作;同时看它申请了哪些文件、命令或网络权限。

兼容 DSH 版本未标注版本不匹配时安装会报错
Node.js 要求未标注来自仓库 package.json 的 engines 字段
前置依赖无需要同时安装的同级依赖
npm 包名未发布到 npm可用 npm view 查看包信息

能做什么

记忆与知识
字段来自项目仓库与公开索引,中文说明由本站标注

适用场景

文件读写

上游标签

cordisdeepseek-harnessdshdsh-pluginnotestodo

验证等级与安全审查

L4 · 沙盒安装通过
自动化流程结论,不是人工审核
验证等级L4 · 沙盒安装通过安装命令在隔离沙盒内真实执行成功,无依赖错误
安全审查低风险静态分析结论,未标注不代表已通过审查
仓库健康度活跃按仓库活跃度与最近更新时间给出
可信度银综合仓库活跃度与验证结果

选择建议

装之前先看这几条
按条目字段给出的判断,不替你决定用不用

值得选的地方

  • 验证等级 L4:在沙盒里真实装过,依赖能装上,不是纸面声明。
  • 静态安全审查为低风险,没有发现明显可疑行为。
  • 有可直接复制的安装命令,不用自己翻文档找入口。

先想清楚的地方

  • 没有声明 Node.js 版本要求,版本不匹配可能装不上。
  • 验证通过不等于安全:插件会读写文件、执行命令或联网,装前先到仓库确认它做了什么。

更适合

  • 需要在记忆与知识场景里给 DSH 补能力的人。
  • 想装完就能用、不想自己排依赖的人。
  • 能接受在本地装依赖、愿意自己确认权限的人。

不太适合

  • 只使用 DSH 官方内置能力、不接受第三方插件的人。

同类对比

6 个同类
同分类里挑最接近的几条,字段以各自详情页为准
插件验证等级安全审查星标兼容版本安装命令
dsh-hub当前 L4 · 沙盒安装通过 低风险 0 未标注 有
ruflo L3 · 安装规范通过 低风险 7.4 万 未标注 无
OpenViking L3 · 安装规范通过 低风险 3.9 万 未标注 无
WeKnora L4 · 沙盒安装通过 低风险 3.3 万 未标注 有
distilly L4 · 沙盒安装通过 低风险 2.5 万 未标注 有
EverOS L4 · 沙盒安装通过 低风险 1.3 万 未标注 有

仓库档案

记忆与知识
字段来自项目仓库与公开索引,中文说明由本站标注
作者 / 维护方HanMaXiao仓库 dsh-hub · 默认分支 main
分类记忆与知识knowledge
收录类型可安装插件声明了 dsh.bundle、可用 dsh plugin add 安装的条目
开发语言未标注仓库主语言
许可证MIT商用或二次分发前请确认条款原文
星标 / 复刻 / issue0 / 0 / 0收录时的数值,非实时
创建 / 更新2026-10-15 · 2026-10-30最后推送 2026-10-30
本站收录2026-10-30首次进入本站目录的时间

README 全文

32.9 KB
作者原文,中文版优先;只做安全转义,不改内容

Hub · 统一管理面板


@hanmaxiao/dsh-hub —— 把 TODO、Note、Prompt 三类条目收进同一个悬浮面板:一个面板、三块标签、完整增删改查,三边停靠随手收起,排序按「一个字段 + 一个方向」配一个分组依据。

它刻意不做三件事:不联网同步、不做多用户协作、不把你的数据塞进浏览器 localStorage。所有条目落在宿主进程的一份 JSON 文档里。

一、安装

DSH 的插件安装只认一种东西:spec。插件页的 Add plugin 输入框和
plugin_manager 接受同样四种写法,选哪一种只取决于你从哪里拿到的包。

方式 A:GitHub(推荐,最省事)

仓库地址直接就是 spec。装之前宿主会先 git ls-remote 探一次连通性,通不过才报网络错。

plugin_manager { action: "install_bundle", target: "https://github.com/HanMaXiao/dsh-hub" }

页面上等价于:侧栏 Plugins → Add plugin,粘贴
https://github.com/HanMaXiao/dsh-hub,回车。

方式 B:npm registry

本包已配置为可发布(publishConfig.access = public)。一旦发布到 npm,
按包名装,版本可省:

plugin_manager { action: "install_bundle", target: "@hanmaxiao/dsh-hub" }
plugin_manager { action: "install_bundle", target: "@hanmaxiao/dsh-hub@1.0.0" }

@hanmaxiao 是你的 npm 作用域,只有账号本人能发布,别人装不到抢注的同名包。
国内网络下插件页默认还会问一遍 npmmirror 镜像。

方式 C:本地绝对路径(开发用)

plugin_manager { action: "install_bundle", target: "D:\\path\\to\\dsh-hub" }

必须是绝对路径。相对路径会以 profile 目录为基准解析,指向 profile 内部,
安装器会直接拒掉。

方式 D:tarball

发一个 .tgz 附件或 GitHub Release 资产,把它的 URL 贴进同一个输入框即可。

装之前先知道的三条

  1. 不要手写 profile 的 package.json 或 cordis.patch.yml。 这两个文件由 Loader 管,手改会被下一次安装或配置写入覆盖。本插件的 bundle 补丁是包内自带的 cordis.patch.yml,它会随安装注入一行 hub-core。
  2. 不要在 profile 目录里跑 pnpm。 profile 目录由 Loader 维护,手工装依赖会破坏它;本插件本身是纯 ESM、无构建步骤、零产物依赖,不需要编译。
  3. install_bundle 一步完成包安装与 bundle 选择,不要用 shell 命令去复现这两步。如果返回了待批准构建脚本清单,本插件不会要求批准任何构建脚本。

安装后确认三件事:list_bundles 里能看到本 bundle、list_plugins 里 @hanmaxiao/dsh-hub 不是 failed、插件详情里显示的是「Hub · 统一管理面板」这个标题而不是包名回退。

装好的插件不会自动升级。 升级就是卸掉再装新版本;这不是本插件的特殊规定,
是 DSH 安装器目前的行为(插件页上也这么写着)。

装不上时报 incompatible-version

安装器会在 pnpm 动手之前先做一次版本兼容检查:本包把
@deepseek-ai/dsh-typert-protocol 钉死在 0.1.7-rc.2,而那个版本声明的
DSH peer 范围如果和你的 DSH 对不上,安装会被在下载之前拒掉,报错里列出
name / version / runtimeVersion 和没满足的 peers。

这时不要改本包去迁就,而是给这一对版本开一次豁免:

plugin_manager { action: "list_version_exemptions" }          # 先拿到 runtimeVersion
plugin_manager { action: "set_version_exemption",
target: "@hanmaxiao/dsh-hub@1.0.0",
runtimeVersion: "<上一步给出的>",
enabled: true, acceptRisk: true }

豁免是精确的 包名@版本 对精确的 DSH 运行时版本,写在 profile 的
compatibility.json 里,不改任何依赖、不改 bundle 选择。acceptRisk: true
需要你明确确认「不兼容的插件可能导致崩溃或数据丢失」,安装器只校验这个标志和
版本号,不校验对话历史。先读下面第七节的排障再开这个 —— 多数时候真正的原因
不是版本不兼容,而是进程里跑的还是旧代码。

从 @local/dsh-hub 迁移

包名 1.0.0 起由 @local/dsh-hub 改为 @hanmaxiao/dsh-hub。这个名字同时是
客户端 manifest 的键、Typert 的 invocation id 和 profile 里的依赖名,所以
改名是一次破坏性安装变更:先 plugin_manager { action: "remove_bundle" }
卸掉旧的,再用上面的任一方式装新的。你的数据在 $dataDir/store.json 里,
不在包里,卸载不会碰它。

生效

  • Host 半边(host.js、lib/、remote.js):Loader 只在首次 import() 某个包名时加载它,Node 的 ESM 缓存按解析后的模块 URL 记账。所以改动了 Host 侧代码必须重启进程,重装、改行 id、改 exports 目标都不会重新加载。
  • Client 半边(client.js):客户端模块表在页面启动时拉取,模块文件由宿主现从磁盘取,所以刷新页面即可。

首次安装时包名此前从未被加载,通常能即时生效;但一旦你改过 Host 侧代码,就走上面的规则:重启进程,再刷新页面。

二、三边停靠与自动收展

面板可以停靠在窗口的左、右、下三条边。

  • 收起时屏幕上只剩一条窄把手,不遮挡任何内容。这条把手是面板唯一的开关 —— 除它之外,界面上没有别的地方能开合面板。
  • 展开的两种方式:指针移入把手(悬停),或点一下把手。
  • 自动收起:指针离开面板后,等一段空闲时间再收回去 —— 你把鼠标移到别处去操作窗口,面板不会一直杵在那儿。
  • 停靠边会被记住:下次打开还是在你上次放的那条边。

三条边都是可选的停靠位:把手停在哪条边,就从那条边唤出面板。选定的边会被记住,所以下次它出现在同一个位置,肌肉记忆不用重新建立。

上边没有停靠位,这是设计上的撤回而不是遗漏:那条边是应用自己的标题栏和对话标题行,面板停在那里要么落进拖拽区(指针永远到不了把手,悬停展开会 silently 失效),要么压住对话标题,两种失败都不报错。旧版本存下的 top 偏好会回退到 right。理由见 DESIGN.md §9。

三、三个标签页、两种视图

底部居中是一个悬浮胶囊形状的标签栏,三个选项:TODO / NOTE / PROMPT。面板里同时只处理当前标签页那一类条目。

视图 样子 说明
网格 卡片铺排 列数随窗口宽度自动重排(repeat(auto-fill, minmax(...)) 加一个 ResizeObserver),窗口变宽变窄都跟着变
条目列表 一行一条 适合条目多、需要扫读的场景

两种视图可随时切换,选择会被记住,下次打开还是你上次用的那个;两种视图各自有独立的加载态。

其余交互约定:

  • 空态只有一个添加按钮,没有别的废话。
  • 加载态有两套独立骨架:网格骨架与条目行骨架,都带脉冲动画。
  • 三个弹窗:创建、删除确认、详情。详情弹窗右上角是复制按钮与关闭按钮。
  • 全文中英双语,所有可见文案走应用的 locale;界面零 Emoji,每个控件都是内联 SVG 加 aria-label。

长列表:窗口化渲染

列表超过 200 条时,面板不会一次性把上千个节点全画出来,而是只画看得见的那一屏,上下各多留 8 行做缓冲 —— 快速滚动时不会出现空白。被跳过的部分用等高的空白占位撑住,所以滚动条的范围和整列完全一样,拖到最后一条照样够得着。

行高先按估值摆放,再按真正画出来的一屏校正一次,滚动位置和内容因此是对得上的。200 条以下走的是和以前完全一样的路径:骨架、动画、折叠行为没有任何区别,唯一的改变是长列表不再让交互变卡。

方向键移动焦点时也只走已经画出来的行 —— 看不见的行不在文档里,焦点不会被留在空气上。

导入与导出

面板顶栏工具栏(标题右侧那排图标:新建、排序、视图、导出、导入、停靠)里有导出和导入两个按钮。

导出是一个弹窗,两组单选:范围(当前标签页 / 全部)和格式(JSON / Markdown)。JSON 与 store.json 同构 —— version、items、statuses 三样,所以导出文件本身就是一份合法的库文件,拿到别处导入就能还原;Markdown 按标签页分节,名称、优先级、状态、时间、备注和附件名排成正文,适合直接阅读。

导入读回导出的 JSON:选好文件(选择框只收 .json / .md),弹窗先列出待导入多少条、其中新建多少、更新多少,确认了才动手。id 已存在的条目会被更新,新 id 的条目会被创建。状态定义先于条目落库:文件里没有的状态由宿主现场创建(id 由宿主铸造),条目里指向旧 id 的地方自动改指新 id —— 否则条目会落在一个不存在的状态上。导入完成后逐条报告成败,失败的条目带上宿主给出的原因;库不会被写坏,失败的条目就是什么都没发生。

三类条目分别有什么字段

TODO Note Prompt
标题 title title title
正文 description + note 备注 text text
归类 category 类别 无 无
标记 priority:low / normal / high / urgent 无 无
状态 status 状态枚举(见下) 无 无
图片 attachments 附件 images images
时间 createdAt、updatedAt 同左 同左

标题是单独存的字段,不再拿正文第一行当标题。新建和编辑时都能单独填。

留空也允许:列表里显示的名称取「标题」,标题为空时退回「正文第一行」——所以旧数据
(本来就没有标题)和随手写的短条目都照常可读,不会出现空白行。标题也计入「大小」,
因为它就是存下来的字节。

正文(description / text)是可选的:标题是对正文的概括,短 TODO 标题就能说清楚时,
只填标题直接点确定就行,不用再补一段重复的话。只有「标题」和「正文」同时为空才会拦下来——
那种条目既没有名字也没有内容,在列表里是一行空白、在详情里是一张空弹窗,所以确定按钮会
保持禁用,直到两者至少有一个非空。编辑时把正文清空是可以的,标题还在就仍然能保存。

时间戳一律 ISO 字符串。宿主校验每一次写入,遇到不认识的 kind、priority 或 status 会点名是哪个字段并拒绝,不会静默吞掉。

完成状态:可编辑的枚举,不是复选框

TODO 条目右侧是一个状态徽标,点开是一列状态 —— 像飞书表格里的「完成状态」单选列,而不是一个勾选框。

出厂带六项,顺序即面板里的顺序:

状态 计入「已完成」
● 待处理 否
● 处理中 否
● 已完成 是
● 处理失败 否
● 暂时搁置 否
● 已取消 是

这份清单归你管:徽标浮层里选「管理状态」,可以新增、改名、换颜色、调整顺序、删除。改名过的状态用自己的文字,没改过的跟着应用的语言走。

清单就是列表的骨架:每个状态在列表里占一个区,区的顺序就是清单的顺序,表头带着这个状态自己的颜色点。所以「处理中」和「暂时搁置」各占一区,而不是混在同一个「未完成」里;没有条目的状态不画表头。每一项还带一个「计入已完成」的开关 —— 勾上,这一项所在的区就按完成时间倒序排(像一本日志,最新的在最上面),没勾上的区照常跟排序菜单走。

两条删除的拒绝是故意的:最后一项不能删(否则 TODO 没有状态可待),还有条目在用的状态也不能删(报错里会告诉你还有几条在用)。把在用的状态删掉、把那些条目悄悄搬到别的状态,是一次你并没有要求的改动。

徽标本身是两种颜色混出来的:一层从左、一层从右,各自用蒙版淡出到对方,中间重叠处才是真正的混色;底下再压一层毛玻璃,所以它贴在卡片上而不是浮在卡片上面。颜色全部取自应用主题令牌,跟着浅色/深色一起变。

旧数据不用管:以前存下的 done 布尔值读进来就是「已完成」或「待处理」,不丢。

四、排序菜单

排序菜单是一个字段 + 一个方向 + 可选分组,三项各自单选。它不是比较器链 —— 不会「先按名称,相同再按日期」。

排序字段与方向

菜单字段 TODO Note / Prompt 递增(递增)的含义 递减(递减)的含义
名称 有 有 A → Z Z → A
修改日期 有 有 旧 → 新 新 → 旧
类型(类别) 有,映射到 category 无 类别字母序 类别倒序
大小 有 有 小 → 大 大 → 小
创建日期 有 有 旧 → 新 新 → 旧
标记(优先级) 有,映射到 priority 无 low → normal → high → urgent 反向:urgent → high → normal → low

「名称」按标题排(标题为空时退回正文第一行),所以你敲的标题就是排序键。

方向只做一件事:把同一个字段反过来。递增永远是「字母靠前、日期靠旧、体积靠小、优先级靠低」,递减就是把它整个倒过来。

大小不是存下来的字段,而是由条目自身的标题、正文、备注加上附件字节现算出来的。

并列怎么办:任何比较都以 createdAt 升序、再以 id 升序收尾。所以键值相等的条目在两次渲染之间不会自己换位置。

菜单里没有的一项:作者

菜单里没有「作者」,而且不会渲染成空的条目。理由:本插件的条目模型里根本没有作者字段。
它不做多用户协作,也不从云端同步,每条数据的作者从头到尾只有你一个。列出一个永远为空的字段是在骗你。

「标题」曾经也在这张被删掉的清单里——那时它和「名称」是同一个东西,因为「名称」本身就是正文第一行。
现在标题是单独存、单独编辑的字段,「名称」是由它派生出来的显示名,两者不再是同一个事实,
所以标题留在表单里,菜单里保留的是「名称」。

分组依据

标签页 可选分组
TODO 无 / 类别 / 优先级
Note 无 / 含图片 / 纯文字
Prompt 无 / 含图片 / 纯文字

分组与排序是正交的两件事:分组决定条目被切成哪几堆,排序决定每一堆堆内的顺序。选了「类别」分组再选「修改日期」递增,得到的是每个类别里各按日期从旧到新,而不是把所有条目混在一起按日期排。

菜单里没有的一项:完成状态

「完成状态」不在分组依据里,也不作为排序字段。完成状态是这份列表的框架(见上一节):每个状态自己就是一个区,再按状态分一次组,等于在一个已经以状态命名的区里再插一层同样的表头。要改状态,直接在条目上点那个徽标 —— 一次点击的事,不必绕到菜单里。

五、数据存放位置

全部数据在一份 JSON 文档里:

$dataDir/store.json

$dataDir 的解析规则:

  1. hub-core 行的 config.dataDir 留空('',即默认值)时,取 $DSH_HOME/plugin-data/dsh-hub;环境变量 DSH_HOME 未设置时退到 ~/.dsh/plugin-data/dsh-hub。
  2. config.dataDir 填了非空值时,按 path.resolve 解析成绝对路径 —— 想把库放到同步盘或备份目录里,改这一处即可,不用动代码。

写入用「写临时文件 + rename」,所以写到一半崩溃不会把你的库截断。文件不存在按空库读;文件损坏则是一次响亮失败,错误信息会点名这个路径,绝不悄悄重置成空库(悄悄清空是比崩溃更坏的结果)。

文档里有两个顶层数组:items 是三类条目,statuses 是你那份完成状态清单。清单随条目存在同一份文档里,所以备份一次就备份了两样东西。

图片:base64 内联,及其取舍

图片不单独落盘,而是以 base64 data URL 直接内联在这份 JSON 文档里。这是一个明确的 v1 取舍:

换来的:整个库就是一个文件。备份就是复制一个文件,迁移就是复制一个文件,用任何文本编辑器、任何 diff 工具、任何版本控制都能看见它。条目和它的图片永远不会走散。

代价的:

  • 体积膨胀约三分之一。base64 用 4 个字符编码 3 字节,编码本身就让每张图凭空多出约 33%。一张 1 MB 的图,进库大约 1.33 MB。留出这个余量再规划库的大小。
  • 整个库一次读写。库越大,每次增删改都要重写整份文档,附件一多会明显变慢。

单张图片的上限:maxImageBytes

hub-core 行上有两个可调项,图片上限就是其中之一:

字段 默认值 作用
dataDir ''(空,即自动解析) 库放在哪个目录
maxImageBytes 2097152(2 MiB) 单张图片的体积上限
  • 默认 2 MiB,按「解码后」的字节算 —— 也就是图片本体的字节数,在 base64 编码之前测量。所以 2 MiB 的图在文档里实际占大约 2.7 MiB。
  • 这是硬上限,不是建议值。 因为整个库是一份 JSON 文档,而面板每次打开都要通过 list 整读整份文档。也就是说,编码后的体积是你每打开一次面板就要付一次的代价。这条上限挡的不是某一张图,是这份文档每一次读入的重量。
  • 超限时 Host 直接拒绝,并在报错里同时告诉你上限值和你这张图的实际大小。你据此去调配置,或者换一张小一点的图 —— 库本身不会被写坏。

要放宽就把 hub-core 行的 maxImageBytes 调大(按字节填),要收紧同理。改的是 bundle 行的 config,不是代码,也不需要动 cordis.patch.yml。

因此实践上的建议:别往 Note/Prompt 里塞高分辨率大图。截图裁到实际需要的尺寸再存,库会长得慢得多,浏览器的渲染也不会因为解码一堆超大图而卡。真的需要更大的图,再去调 maxImageBytes。

附件的类型白名单

附件和图片只收四种栅格图:image/png、image/jpeg、image/gif、image/webp。文件选择框也只列出这四种类型。

image/svg+xml 会被拒绝,理由是安全不是体积:SVG 是一份能携带脚本(``、事件属性、外部引用)的 XML 文档,而所有附件都以 data URL 的形式内联在面板渲染的那一份文档里 —— 收进来,就是让面板去显示它。PNG、JPEG、GIF、WebP 携带不了任何可执行的东西。代价是真实的:有矢量素材的用户得先导出一次;但一个会运行的文件不是任何人能恢复的状态。

白名单只拦写入:改动之前存进去的 SVG 附件照样读得出来,旧库不会被这条规则锁死 —— 和 maxImageBytes 同一个口径,两个闸都只管「还能不能写」,不管「已经写了的还算不算数」。拒绝时报错会点名收到的类型和这四种合法类型。

六、主题

跟随应用,插件自身不提供独立主题。

  • 面板只用 ctx.theme.setTheme('light' | 'dark' | 'system'),三态是亮色、暗色、跟随系统。
  • 三态偏好归应用管。插件不再维护第二份主题设置 —— 两份设置迟早会打架。
  • 所有颜色取自 --dsw-alias-* 主题令牌,插件不写死字面色值(图标图形本身除外)。

想换主题,在应用的主题设置里切,面板跟着变。

七、常见问题

面板一直显示失败态

这是设计,不是崩溃。 客户端与宿主之间唯一的桥是一条 Typert Remote(remote.js + typert.js,SERVICE_KEY = 'hub')。host.call 和 styles.insert 属于 cordis_run 的动态插件运行时,对 dsh.client 这类 bundle 不可用,所以没有别的路可走。

这条 Remote 到页面要经过两步,缺一不可:

  1. 客户端自己挂载它。 dsh-typert-loader 只在宿主侧发现包的 ./typert 导出;基座里客户端侧的挂载全是 @deepseek-ai/dsh-api-remotes 里那批生成的第一方贡献项,不会替你挂第三方包的命名空间。所以 client.js 必须自己调用 ctx.remote.$mount(),贡献项与 typert.js 一一对应。
  2. 命名空间是一个独立服务。 $mount() 会把每个命名空间装成一个独立的 Cordis 服务,键名是 remote.。所以面板读的是 ctx.get('remote.hub'),不是 ctx.get('remote')?.hub —— 那个 carrier 上只有 $mount、$stream、$host、$on,永远没有 hub。
  3. 发布出去的方法名不能撞命名空间服务自己。 网关的 RemoteNamespaceService 自己就有 remove、install、has、empty、name、methods、namespace、invokeRemote、ctx,挂载时同名的方法会被拒。remove 因此不能用:删除发布为 delete,由 implementation: 'remove' 转发到宿主那个方法(网关按 descriptor.implementation ?? descriptor.method 分发)。

面板在每次调用时重新读取 ctx.get('remote.hub')(承载方可能在首次渲染之后才到,也可能在卸载时消失)。读不到时它显示一个持久的失败态:

  • 不会悄悄退回 localStorage —— 那意味着你的数据看起来好好的,实际存在另一个地方,等你发现时已经分叉了;
  • 不会抛异常把页面打坏;
  • 挂载被拒绝时也不会把入口整个判失败 —— 面板照样出现,只是把被拒绝的原因显示出来。

失败态里那行红色小字会直接说明是哪一步被拒的:

小字 含义
this composition has no Remote carrier: ctx.get("remote") is absent 组合里没有 @deepseek-ai/dsh-api-remotes 那一行
mounting the Hub Remote contribution failed: …conflicts with its namespace service 有发布出去的方法名撞上了命名空间服务自己的成员
ctx.get("remote.hub") is not mounted 挂载成功过,但承载方随后消失了(插件被停用)

标题填了但保存后没生效:宿主会按设计丢弃它不认识的字段(未知键是客户端的事,不该让一次保存失败)。
所以如果宿主进程比面板旧——比如你改了 lib/model.js 却只刷新了页面——title 会被静默丢掉,
列表里又退回显示正文第一行,全程没有任何报错。

面板现在会自己发现这件事:宿主返回的条目里凡是缺少 §5 的字段(缺键,不是空值),
就说明宿主更旧,面板会挂一条提示,点名丢了哪个字段、并让你重启 Host 进程。

自己确认的话直接看数据文件,键里没有 title 就是宿主旧了:

Get-Content "$env:DSH_HOME\plugin-data\dsh-hub\store.json" -Raw | ConvertFrom-Json |
ForEach-Object { $_.items[0].PSObject.Properties.Name -join ',' }

修复只有一步:重启 DSH 进程(重装、改配置、刷新页面都不管用)。
重启后把你之前丢掉的标题重新编辑一次补上——旧条目本身就是空的,不会被追溯恢复。

还有一种情况:客户端挂载成功了,但宿主那个 hub 服务根本没起来——这时网关要等到第一次调用才报 active Service "hub" is unavailable,面板表现和上面一样。宿主侧的原因只有两个:

  1. 进程里跑的不是最新代码。 HubRemote 必须声明 static inject = ['hub-store'];没有它,HubRemote 会在构造时因为读不到 store 而抛错,而那一行仍然是 ACTIVE,从外面看完全正常。改过 Host 侧就重启进程。
  2. hub-store 没起来,比如 store.json 损坏——HubStore 在构造时响亮失败,路径写在消息里。

宿主侧有一行日志可以直接确认,启动后(或改完 Host 代码重启后)在 Host 日志里找:

dsh-hub: {"service":"hub","mounted":true,"namespace":"hub","boundTo":"hub","methods":["list:direct","create:direct","update:direct","delete:direct","createStatus:direct","updateStatus:direct","deleteStatus:direct"]}

mounted:false 或这行根本没出现,就是宿主半边没挂上,和客户端无关。挂载失败时同一位置会打 dsh-hub: the Hub Remote did not mount: <原因>。

最常见的原因是组合里没有承载这条 Remote 的行,通常是 @deepseek-ai/dsh-api-remotes 那一行缺失。

按顺序排查:

  1. plugin_manager { action: "list_plugins" } —— 确认 @hanmaxiao/dsh-hub 在列表里,且状态不是 failed。是 failed 就先看诊断。
  2. plugin_manager { action: "list_bundles" } —— 确认本 bundle 已选中。若状态是 restart-required,说明改动不在当前进程生效,重启 Host 进程。
  3. 确认当前组合里确实有 @deepseek-ai/dsh-api-remotes 这一行,以及 typert-loader 没有对 typert.js 报 manifest 校验警告(例如 codec 缺 create() 工厂那类)。
  4. 确认宿主半边跑的是最新代码:改过 Host 侧(remote.js / typert.js / lib/)就必须重启进程,重装和改配置都不管用;只改 client.js 时刷新页面就够。改名或改 manifest 属于前者。
  5. 刷新页面让 Client 半边重新拉取模块表。
  6. 刷新后仍失败 —— 看页面 console 里对应的那一行,以及面板失败态里那行红色小字。插槽条目里抛的错只在 console 留一条记录,不会渲染成可见错误。

面板怎么开、怎么收

面板只有一个入口:屏幕上那条窄把手。收起状态下它贴在窗口边缘,不遮挡任何内容。

  • 展开:指针移到把手上即展开,或者点它一下。(只有把手,不是整条边——否则沿着边缘蹭一下面板就弹开了。)
  • 收起:指针离开面板片刻后自动收起,不需要你手动去关。
  • 换边:把手可以停靠左、右、下三条边中的任意一条,停在哪里就从哪里唤出,选择会被记住。
    上边没有把手:那条边是应用自己的标题栏和对话标题行,面板停在那里要么被拖拽区挡住收不到悬浮,要么压住对话标题,两种都不会报错,所以干脆不做。

所以「找不到面板」通常不是它坏了,而是你没往它停靠的那条边看 —— 它就贴在窗口边缘。若整条边都找不到把手,那才是面板没有激活,回头看上一条的排查。

数据能备份吗

能,整个库就是一个 store.json,复制走即可。要搬到别处,改 hub-core 行的 config.dataDir 指过去,然后把原文件复制过去。

改了 dataDir 之后数据「不见了」

不是丢了,是换了地方。dataDir 决定的是读哪一份库;改配置不会搬迁旧数据,记得自己把原来的 store.json 复制过去。

图片存不进去,报「超过上限」

默认单张上限是 2 MiB(按解码后字节算),见第五节的 maxImageBytes。报错里会同时给出上限值和这张图的实际大小。两条路:换一张小一点的图(多数时候你只是截图没裁),或者把 hub-core 行的 maxImageBytes 调大。调之前先想清楚 —— 面板每次打开都要整读整份文档,图越大,每次打开越慢。

还有一类拒绝和大小无关:类型。附件只收 PNG / JPEG / GIF / WebP 四种栅格图,SVG 会被拒掉,原因和白名单见第五节。

反复打开面板,宿主会反复读整个文件吗

不会。解析后的文档按文件的修改时间 + 大小缓存在宿主进程里:重复的列表和状态查询直接命中缓存,一次写入之后最多再解析一次,不是每次查询都把整份文档重新读一遍、重新解析一遍。

文件被别的程序改过,下一次查询仍然会重新读取 —— 缓存不会给你旧数据。读取的三种结果也没变:文件不存在是空库,文件损坏是点名路径的响亮失败。

要注意缓存省掉的是宿主侧的反复读取和解析:文档本身仍然整份越过 Remote 到面板,这部分成本一点没变,maxImageBytes 依旧是对它的约束(见第五节)。

两个地方同时改,数据会冲突吗

同一个库文件的每次写入(新建、修改、删除,以及状态定义的增删改)都在一条队列里完成「刷新 → 修改 → 落盘」三连:两个并发写不会再互相覆盖、丢掉其中一条;写临时文件再加 rename 的原子写入也不变。

这条队列是进程内的。两个 DSH 进程指向同一个 dataDir 从来不是受支持的用法 —— 原子写入保证文档永远不会是写了一半的,但后写的那一方仍然会覆盖先写的那一方。

store.json 报错了

如果它损坏或不可读,面板会明确报错并点名完整路径,而不会假装这是空库。修法:先手工把那个文件挪走备份,再让面板以空库重新开始(代价是那一库的数据没了,所以先备份)。不要指望它自动重建。

八、包结构

下列结构取自 package.json 的 exports 与 files 字段 —— 那是这个包对外的契约。files 同时覆盖运行时文件与 cordis.patch.yml、locale/*.json、icon.svg;locale 与图标在插件不激活的情况下就能被 Plugin Manager 与设置页读取(标题、描述、图标)。

tests/ 是唯一的例外:它只存在于仓库里供 node --test 运行,不在 files 中,不随包发布。

dsh-hub/
├── package.json          私有 ESM 包:exports / files / dsh.bundle.patch / dsh.client
├── cordis.patch.yml      注入一行 hub-core,config 上两个可调项:dataDir 与 maxImageBytes
├── icon.svg              包图标
├── DESIGN.md             设计契约(权威)
├── README.md             本文件
├── locale/
│   ├── zh.json           Plugin Manager 显示元数据:meta.title / meta.description
│   └── en.json           同上,key 集与 zh.json 完全一致
├── index.js              Host 入口,导出 apply(ctx, config)
├── host.js               挂载磁盘 store 与 Remote,注册全部资源走 ctx.effect
├── remote.js             HubRemote:客户端唯一的数据通路
├── typert.js             与 remote.js 一一对应的 Typert manifest
├── client.js             面板全部界面与交互(单文件,不可拆)
├── lib/                  宿主侧纯逻辑:id 生成、条目校验、状态定义、原子 JSON 存储
└── tests/                node --test 覆盖 lib/

Client 半边是单文件,这是协议限制不是偷懒:manifest.ts 规定客户端 entry 名必须等于包名,一个包只能有一个 client.js。

九、维护须知

  1. 不要引入 emoji。 全部控件用内联 SVG 加 aria-label。
  2. 不要写死颜色。 一律 --dsw-alias-* 令牌。
  3. 不要 require 任何 Harness Client 包。 官方明确禁止,控件必须自己写。
  4. 不要往 document.body 追加第二套应用。 面板挂在 shell.overlay 座位上。
  5. 排序规格改前先读 DESIGN.md §6。 「作者」与「标题」是被故意删掉的,不是漏做的。
  6. 验证命令:node --test(跑 tests/)。

十、参与开发

pnpm install        # 只有测试依赖,插件本身零产物
node --test         # 唯一的验证命令;CI 跑的就是这一条

没有构建、没有打包、没有 lint、没有类型检查 —— 这不是省事,是协议要求:
Loader 直接按文件原样 import() 本包,加一层构建只会让 files 和仓库对不上。
所以一个 bug 要么被 node --test 逮到,要么被用户逮到,别指望编译期。

  • DESIGN.md 是设计契约,冲突时它说了算。
  • AGENTS.md 是改这份代码前必读的约束,每条都写了为什么。
  • 改动走 PR。main 受保护:必须过 node --test,且需要一个人 approve,
    不能直推、不能强推。

十一、许可证

MIT。DeepSeek Harness 与本插件的官方包同为 MIT。

常见问题

6 问
答案由本站依据条目字段整理,不引用上游原文
dsh-hub 怎么安装?

在 DSH 里执行 `dsh plugin --profile web add github:HanMaXiao/dsh-hub` 即可。

dsh-hub 的验证等级是什么意思?

L4 · 沙盒安装通过。安装命令在隔离沙盒内真实执行成功,无依赖错误

dsh-hub 安全吗?

低风险。

dsh-hub 装之前要先确认什么?

先看它会读写哪些文件、执行哪些命令、连接哪些网络地址;验证等级只说明装得上、跑得起来,不等于安全。涉及密钥或系统权限的,建议先在隔离环境试装。

dsh-hub 用的是什么许可证?

上游标注为 MIT。商用或二次分发前请到仓库确认许可证原文。

这条记录的数据可靠吗?

本站按仓库名去重后合并整理,字段以作者仓库与 npm 元数据为准;安装命令、仓库地址与 README 保持原文,带 L4 / L5 标记的安装命令已在隔离沙盒里真实跑通。

同名或近似条目

3 条
不同作者的仓库分开列,请确认你要装的是哪一个
记忆与知识
按星标排序,可作替代方案

ruflo

ruflo

L3 · 安装规范通过 7.4 万 星

OpenViking

OpenViking

面向 DeepSeek Harness 的 OpenViking 记忆与上下文插件:pre-step 自动召回与画像注入、会话捕获、`vik

L3 · 安装规范通过 3.9 万 星

WeKnora

WeKnora

把 WeKnora 知识库接入 dsh 的四个只读工具:列出知识库、混合检索原文片段、按顺序还原单篇文档,以及直接取用 WeKnora 自己

L4 · 沙盒安装通过 3.3 万 星

distilly

distilly

Distilly — Distill how they think into reusable Skills for any Agent o

L4 · 沙盒安装通过 2.5 万 星

EverOS

EverOS

L4 · 沙盒安装通过 1.3 万 星

MemOS

MemOS

L4 · 沙盒安装通过 1.2 万 星

插件的元信息来自作者仓库;分类、验证等级与安全结论的中文说明为中文整理,最近核验 —。