Files
Pine a53769955b feat(plugin): plugin marketplace, durable install persistence, skill manager
Add an in-app plugin marketplace fed by a remote web catalog: the host
plugin-inventory gateway gains marketplaceList/Install/Uninstall remotes,
a durable per-user install table reconciled against the actual profile, and
git/npm/tarball/bundle install paths with non-interactive git and vendored
pnpm. The Web Settings surface gains a sibling 插件市场 tab that lists the
catalog with recommended badges, repository links, and a combined sort
(recommended first, then catalog priority, then id).

Add a host + client skill manager: list local skills by direct filesystem
discovery, and install/uninstall/toggle/edit their SKILL.md from git/npm/
tarball/local sources in the writable user root.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-15 17:04:36 +08:00

31 lines
6.3 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# @deepseek-ai/dsh-host-plugin-inventory
[English](README.md) | 中文
当前 Cordis Loader 树的 Host 投影,带逐插件启用/停用。`PluginInventoryGateway` 注册 `pluginInventory` 服务,并发布两个由 Typert 生成的直接 Remote`pluginInventory/list``pluginInventory/setEnabled``list` 直接读取 `ctx.loader.entries()`,跳过结构性的 group 行,再按 Loader 顺序返回其余条目,并且只包含 Loader 条目 id、模块标识、有效启用状态与当前根 Fiber 阶段。
阶段为 `pending``loading``active``failed``unloading`;条目没有存活的根 Fiber 时则为 `null`。该快照刻意只表示调用当下:Loader 仍是唯一的生命周期权威,本包不拥有缓存、历史、来源模型或事件流。`setEnabled` 通过 `ctx.loader.update` 实时切换单条条目,并把显式 `disabled` 覆盖写进 profile 的用户补丁层,使选择在重启后保留(bundle 默认禁用的行需要 `disabled: false` 覆盖才能保持启用)。
每条条目带 `protected` 标记。`src/required.ts` 中的守卫默认开放,含两个可由代码编辑的名单:`REQUIRED_PLUGINS`(黑名单,即不可停用的承重核心——入口树、Remote RPC 主干、session 与 agent 主干)与 `USER_TOGGLEABLE_PLUGINS`(白名单,覆盖黑名单、对显式可开关的插件生效);未列入任何名单的插件默认可切换。随包 base bundle 的完整依赖图分类见 [`docs/plugin-system.md`](../../../docs/plugin-system.md)。`setEnabled` 拒绝停用必需插件;`apply` 真正失败的插件会经 loader 自身的启动错误被拒绝,而仅仅等待依赖(PENDING)的插件会保持启用,待依赖就绪后自动激活。Web 插件列表 tab 渲染单一扁平列表,包含所有条目:每项按真实启用状态显示,可切换插件带"启用"或"停用"按钮(这样 bundle 默认禁用的插件也能重新启用),必需插件只显示只读说明。
网关还通过 `availableBundles`/`installPlugin`/`installedBundles`/`uninstall` 管理安装。`availableBundles` 列出 `src/bundles.ts``AVAILABLE_BUNDLES`)中策展的离线可安装可选 bundle;该目录目前为空,直到有可选 bundle 随包——profile 的默认 bundle`dsh-base``dsh-web-app``dsh-image-recognition-bundle`)是部署的一部分,非可选插件,`uninstall` 拒绝移除它们。`installedBundles` 列出 profile 中用户安装的依赖(即可卸载的那些)。`uninstall``pnpm remove` 完整移除用户安装的依赖(同时删除依赖与它声明的 bundle 层,和安装一样受 `dshAllowPluginInstall` 门禁),对已组合的离线可选 bundle 仅取消组合,并拒绝内置默认 bundle。`installPlugin` 在可写的 profile 目录对任意 `pnpm add` specifier 运行 pnpm——用内置进 harness 的 pnpm,或当没有内置 pnpm(如开发 checkout)时用 PATH 上的 `pnpm`,只有两者都不存在才显式报错——裸 npm 包名、tarball 路径或 URL(含 GitHub archive `.tar.gz`)、git/GitHub 仓库地址,所有社区安装来源共用同一条路径。registry 安装受 `dshAllowPluginInstall` 上下文标志门禁,仅桌面启动会开启;裸包名 spec 会依次尝试 `src/install.ts` 中排序的 `INSTALL_REGISTRIES` 镜像源列表,直到其中一个成功——官方 npm 源排在最后作为保底,只有所有镜像源都不可达才报错,而 git/tarball/path spec 只运行一次、不经过镜像源。裸包名 spec 还会带上 `--minimum-release-age-exclude`,使刚发布的插件也能安装。
由于 pnpm 11 默认拦截依赖的构建脚本,当有脚本被拦截时安装分两阶段:首次调用把被拦截的包作为 `pendingBuilds` 返回而非失败,设置页 tab 以沙箱警告展示这些包供用户逐包同意,重试时把已同意的集合放在 `consentBuilds` 里——宿主把恰好这些包的按包 `allowBuilds` mappnpm ≥11)与 `onlyBuiltDependencies` 数组(pnpm 10)直接写进 profile 的 `pnpm-workspace.yaml`,然后重新安装。直接写配置而非运行 `pnpm approve-builds`,不依赖这些包是否处于 pnpm 的 pending 状态。当 boot 提供了 `dshReloadProfile` 句柄时,网关在写入后重组合运行中的树,使插件立即生效(`restartRequired: false`);否则安装持久化 profile 清单,需重启生效(`restartRequired: true`)。公开 payload 类型位于 `./types`Typert 生成由 `./typert``./remote` 导出的 Host 和 Client Remote 产物。
插件市场通过 `marketplaceList`/`marketplaceInstall`/`marketplaceUninstall` 暴露。目录放在静态网站宿主上(默认 `https://deepseek.pinesound.cn/plugins/plugins.json`,可用 `DSH_MARKETPLACE_URL` 覆盖):一个索引 JSON 列出插件,旁边每个插件的 JSON 规定安装方式(`git``npm``tarball``bundle`)以及 pnpm specifier 和依赖名。`marketplaceList` 叠加持久化安装表——`$DSH_HOME/plugin-marketplace/installed.json` 下的一小份 JSON 文档——把每条标记为已安装;该表是判断是否已安装的权威依据。`marketplaceInstall` 把安装方式映射到既有安装路径(git/npm/tarball 走 registry Remotebundle 走离线组合),成功后在表中记录安装,并沿用同样的两阶段构建脚本同意流程;`marketplaceUninstall` 从表中解析依赖名,执行既有卸载并删除表行。每个插件的 spec URL 由固定目录基地址加插件 id 推导(绝不取自目录内容),因此恶意目录无法把应用指向任意 URL。
该服务仅供 Remote 使用,刻意不声明同进程 Cordis `Context` merge。Client 包通过显式的 [`api-remotes`](../../api/remotes/README.md) 组合消费它,而不导入 Host 实现。
## 模型体验
无,因为这个仅限 Host 的清单投影不注册提示词、工具、消息或提供方请求。
#### KV Cache 影响
无;本包从不组装模型输入。
## 已知限制与暂缓事项
- **仅表示调用当下** —— 结果不包含持久的失败历史或订阅;只要不存在存活的根 Fiber,就会报告 `null`,而不区分其原因。
- **无来源、不能增删** —— 服务不识别条目由哪个 bundle、profile 或 override 引入,也不能添加或移除插件。启用/停用持久化到 profile 的用户补丁层;profile 未挂载(任何 bundle 都没有)的行无法从这里切换。