插件库存
实时看一下当前部署里装了哪些插件、它们处于什么状态。DSH 暴露一个只读的 pluginInventory 服务,通过 Remote 把当前 Cordis Loader 树投影给客户端;Web 设置里的「插件」标签页就是它的可视面。
只读投影:ctx.pluginInventory
packages/host/plugin-inventory(包名 @deepseek-ai/dsh-host-plugin-inventory)的 PluginInventoryGateway 注册 pluginInventory 服务,并发布一个由 Typert 生成的直接 Remote:pluginInventory/list。
每次调用都直接读 ctx.loader.entries(),跳过结构性的 group 行,按 Loader 顺序返回其余条目——只带四个字段:
| 字段 | 含义 |
|---|---|
entryId | Loader 树条目的稳定标识(branded PluginEntryId) |
moduleName | 该条目 import 的精确模块标识 |
enabled | 有效启用状态(含被禁用的祖先 group) |
fiberPhase | 当前根 Fiber 阶段,无根 fiber 时 null |
// packages/host/plugin-inventory/src/index.ts —— 直接读 Loader,不建第二份缓存
@Remote('list')
list(): PluginInventorySnapshot {
const entries: PluginInventoryEntry[] = []
for (const entry of this.ctx.loader.entries()) {
if (entry.options.group) continue
entries.push({
entryId: pluginEntryId(entry.id),
moduleName: entry.options.name,
enabled: !entry.disabled,
fiberPhase: entry.fiber === undefined ? null : FIBER_PHASE[entry.fiber.state],
})
}
return { entries }
}
fiberPhase 取值
| 取值 | Cordis Fiber 状态 | 含义 |
|---|---|---|
pending | PENDING | 尚未开始加载 |
loading | LOADING | 正在加载模块 |
active | ACTIVE | 已激活运行 |
failed | FAILED | 加载/激活失败 |
unloading | UNLOADING | 正在卸载 |
null | DISPOSED | 无存活的根 Fiber |
快照是当下的点:Loader 仍是唯一的生命周期权威,本包不拥有缓存、历史、来源模型、事件流或修改路径。服务是 Remote-only,刻意不声明同进程 Cordis Context merge——Client 包经 api-remotes 装配消费它。
// packages/host/plugin-inventory/src/types.ts —— 阶段投影
export type PluginFiberPhase =
| 'pending' | 'loading' | 'active' | 'failed' | 'unloading' | null
Web 设置里的「插件」标签页
packages/client/ui-settings-plugin-inventory(@deepseek-ai/dsh-client-ui-settings-plugin-inventory)是浏览器插件,注册 id 为 all 的本地化 settings.plugins.tab 贡献。「插件」分区拥有导航入口与标签栏。
- 懒加载:首次选择标签页才挂载组件,经
api-remotes懒调用ctx.remote.pluginInventory.list() - 渲染一个可搜索的双列卡片目录;每张收起的卡片 = 模块短名称标题 + 有效启停标签 + (已启用时的)彩色根 fiber 状态圆点
- 展开卡片后:Loader 树条目 id、有效配置状态,已启用条目额外显示 Cordis 状态;已停用条目省略"未挂载"运行状态
- 搜索匹配 module 名与 entryId;加载/空/无匹配/失败状态只属于已挂载组件,读取失败可重试且不暴露传输细节
// ui-settings-plugin-inventory/src/client/index.ts —— 经 slots 注册,不 import 分区拥有方
ctx.slots.inject('settings.plugins.tab', () => ctx.slots.register({
name: 'settings.plugins.tab', id: 'all', order: 10,
label: () => t('tab'), locale: NS, inject: injected,
}, PluginInventorySettingsTab))
状态点用 data-phase 上色:启用且 active → 表示成功的绿,failed → 错误色,loading → 进行中色,其余为灰(见 PluginInventorySettingsTab.module.css 的 .statusDot[data-phase='…'])。
与相邻能力的区别
| 能力 | 关系 |
|---|---|
插件安装管理 与 plugins.md | 管理面(启用/停用/安装);本能力是只读投影,没有任何修改路径 |
| tool-cordis | 模型侧的工具;本能力是部署/宿主侧的 Loader 树视图,无工具或提示词注册 |
ctx.loader | 生命周期的唯一权威;pluginInventory 只是它的只读镜像 |
本能力不注册任何提示词、工具、消息或提供方请求(README Model Experience: None)。
验证
# 组合树里确认服务与 Remote 贡献存在
dsh web --dump-config | grep -iE "plugin-inventory|pluginInventory"
# 打开 Web 设置 →「插件」标签页,看可搜索的卡片目录与彩色状态圆点
下一步
- Remote API 网关:
pluginInventory/list所经由的网关与$mount()机制 - Web UI 架构:双进程与 slot 体系的背景
- 插件解剖:dual-face 插件形态