dsh-plugin-steward · 插件管家
English · 中文
Plugin management for DeepSeek Harness, reachable from the chat composer: list every installed bundle, switch plugins on and off, edit each plugin's settings, and hide a single plugin's tools and MCP servers from one conversation.
English · 中文
dsh-plugin-steward adds one button to the composer (🧩 插件) and one settings page. Its host half registers a few
same-origin HTTP routes inside the running harness process; its client half is a single unbundled module loaded by
the web UI. It starts no extra process and stores no credentials.
![]()
English
What it does
| Feature | Behaviour |
|---|---|
| Catalog | Lists every installed bundle (third-party and built-in) and every loader row with title, version, description, package name and current state. |
| Global on/off | Bundles go through pluginManager.setBundleEnabled, rows through setPluginEnabled. The result is reported as applied / restart-required / overridden / failed. The plugin refuses to disable itself (HTTP 409) so the entry point cannot disappear. |
| Per-plugin settings | The ⚙ next to each row opens a settings drawer. The host reads that row's schemastery Config and derives the form: string, number (with min/max), boolean, enum and JSON controls, each with default and a reload marker when the field is volatile. Saving sends only changed fields through configEditor.edit(). |
| Per-conversation gating | The This conversation tab hides a plugin's tools or a whole MCP server from the model for one conversation. It calls agent.ctx.tools.restrict() on that agent, so it applies to an already-running conversation from the next request. Policies are saved per session and re-applied after a reload or restart. |
| Automatic descriptions | When a plugin ships no meta.description, the host derives one sentence (≤ 90 chars) from the plugin's own README, client slot ids or package.json. Author-written descriptions always win; ✨ regenerates on demand and the source is labelled. |
| New-plugin discovery | A silent scan every 5 minutes records newly appeared bundles and rows as pending. The button shows an orange dot; the panel offers one click to acknowledge them. |
| Kernel section | Bundles that ship with DSH itself and are actually assembled in the current profile are listed on a separate read-only 内核 tab with 🔒. Their enable logic stays isolated in the backend from third-party plugins. |
| Coexistence | If a capability-panel style plugin is installed (dsh-capability-panel, dsh-capability-composer, @super_camel/dsh-plugin-panel), the panel shows a note pointing at it and does not reimplement its live context state. |
Entry points:
- 🧩 插件 at the left of the composer tool row (
conversation.input.left) → floating panel with 全局 and 本对话 tabs. - Settings → 插件管家 (
settings.section) → discovery settings, profile paths, per-conversation policy list.
Install
# from GitHub
dsh plugin add github:Eveneve-night/dsh-plugin-steward
# from npm (if published)
dsh plugin add dsh-plugin-steward
Or install the directory itself as a bundle with the plugin manager:
plugin_manager action: install_bundle
target: <path to this checkout>
The bundle adds one row to the composition:
- insert:
- id: plugin-console
name: 'dsh-plugin-steward'
Reload the Web UI once (the client half is injected at page load). After editing the source,
set_plugin on include:plugin-console with enabled: false then true.
HTTP routes
All routes are same-origin under /plugin-console/api and are called by the client half.
| Method | Path | Purpose |
|---|---|---|
| GET | /catalog |
Full catalog, discovery state, kernel section, profileBundles; ?ack=1 also acknowledges |
| GET | /status |
Pending count (orange dot) and client-half load state |
| POST | /toggle |
{kind:'bundle'|'row', id, enabled} |
| POST | /ack |
Record the current plugin set as known |
| GET | /config?id=<entryId> |
Derived form description and explicit values for one loader row |
| POST | /config |
{id, config} → validated, persisted and reloaded by the Loader |
| GET | /sessions |
Running sessions plus every saved per-conversation policy |
| GET | /session?id=<sessionId> |
Gate view for one session: visible tools, tools grouped by plugin, MCP servers, denied list |
| POST | /session |
{id, disabled: string[], disabledMcp: string[]} → saved and applied immediately |
| DELETE | /session?id=<sessionId> |
Clear one conversation's policy and revoke its restriction |
| GET/POST | /settings |
Discovery settings (auto-acknowledge) |
| GET | /describe?id=<package>&refresh=1 |
Derive a description from the plugin's own files |
Files written
<profile>/.plugin-console/state.json— known bundles/rows, pending discoveries, settings.<profile>/.plugin-console/sessions/<sessionId>.json— per-conversation policy (disabled,disabledMcp).
Nothing else is modified; global switching goes through the platform's own plugin manager.
Limits
- Pure UI plugins (wallpaper, widgets, sidebars) cannot be toggled per conversation. Client plugins load at profile level and their slot registrations are global. For those the steward offers global on/off and a way to open their settings.
- Per-conversation gating covers globally registered tools. Tools registered inside an agent scope (by a preset
or composition) stay visible —
tools.restrict()does not reach them. Globally registered plugins and MCP servers are covered. - The settings form is per loader row. A bundle with several rows lets you switch rows; a row without
Configshows "nothing to configure". - Nested object/array fields fall back to a JSON text area.
- Automatic descriptions are heuristic. README first, then client slot ids, then
package.json; the source is labelled and can be regenerated.
Test
node dev/smoke-client.mjs # client half: load, 3 slot registrations, panel / this-conversation / settings render
node dev/test-describe.mjs # description derivation against 6 installed plugins
node --check lib/index.js # host half syntax
中文
它做什么
| 功能 | 行为 |
|---|---|
| 统一目录 | 列出全部已安装 bundle(第三方与内建)和全部 loader 行,带标题、版本、描述、包名与当前状态。 |
| 全局启停 | bundle 走 setBundleEnabled,行走 setPluginEnabled,结果区分「已生效 / 需重启 / 被覆盖 / 失败」。插件管家不允许关掉自己(HTTP 409),免得入口消失。 |
| 单插件设置 | 每行右侧 ⚙ 打开设置抽屉:宿主自省该行的 schemastery Config 生成表单(字符串、数字带范围、布尔、枚举、JSON 五类控件,带默认值与「需重载」标记)。保存只提交改动过的字段,经 configEditor.edit() 校验并写回。 |
| 按对话放行 | 面板「本对话」页把某个插件的工具、或某个 MCP 服务器,在这一轮对话里对模型隐藏。它在该 Agent 上调 agent.ctx.tools.restrict(),所以对已开始的对话也生效,下一个请求即可见。策略按会话保存,重载或重启后自动补应用。 |
| 自动描述 | 插件没写 meta.description 时,按它自己的 README、客户端 slot id、package.json 生成一句话(≤90 字);作者写的描述永远优先,✨ 可手动重新生成,并标注来源。 |
| 自动发现 | 每 5 分钟静默扫描一次,把新出现的 bundle / 行记成「待纳入管理」,按钮上出现橙点,面板里一键认领。 |
| 内核分区 | DSH 本体自带、且当前 profile 真正组装的包单独列成只读「内核」页(🔒);它们的启用逻辑在后端与第三方插件完全隔离。 |
| 与能力面板共存 | 检测到 dsh-capability-panel / dsh-capability-composer / @super_camel/dsh-plugin-panel 时只显示一条指向提示,不重复实现它的实时上下文状态,也不做双向同步。 |
入口:
- 输入框工具行左侧的 🧩 插件(
conversation.input.left)→ 浮层面板,含「全局 / 本对话」两档。 - 设置 → 插件管家(
settings.section)→ 发现设置、目录路径、按对话策略清单。
安装
dsh plugin add github:Eveneve-night/dsh-plugin-steward
或者用插件管理器把本目录当作 bundle 安装:
plugin_manager action: install_bundle
target: <本仓库的路径>
组合里会插入一行 plugin-console(见上文 YAML)。刷新一次 Web UI 使客户端半生效;改源码后用
set_plugin 把 include:plugin-console 先设 false 再设 true 即可重载。
接口与写入的文件
接口表同上,全部挂在 /plugin-console/api 下。持久化只有两处:
<profile>/.plugin-console/state.json(已知集合、待认领项、设置)与
<profile>/.plugin-console/sessions/<sessionId>.json(按对话策略)。其余不做改动——全局开关走平台自己的插件管理器。
已知边界
- 纯 UI 插件(壁纸 / 挂件 / 侧栏)无法按对话开关:客户端插件在 profile 级加载,slot 注册是全局的。这类插件只提供全局启停与「打开它的设置」。
- 按对话放行只覆盖全局注册的工具:preset / composition 在 agent scope 里注册的工具,
tools.restrict()本身也限制不到。 - 设置表单是按 loader 行的:多行 bundle 可在头部切换行;没有
Config的行显示「没有可配置项」。 - 嵌套对象 / 数组字段退化为 JSON 文本域。
- 自动描述是启发式的:可能不准,所以标注来源并可手动重生成。
自测
node dev/smoke-client.mjs
node dev/test-describe.mjs
node --check lib/index.js
License
MIT
No comments yet. Be the first to write one.