Bundle
dsh-agent-plugin-bridge
Generic Agent Plugins 1.0 → DeepSeek Harness bridge: discovers plugin.json packages, registers skills and MCP servers.
- Source
- dubeno
- License
- MIT
- Updated
- Updated 4 hours ago
Readme
# dsh-agent-plugin-bridge
[](#dependency-strategy-read-this)
[](LICENSE)
[English](#english) · [中文](#中文)
A generic **Agent Plugins 1.0 → DeepSeek Harness bridge**. Drop it into any
DSH profile and it will scan a list of plugin roots and plugin directories,
pick up every package that has a `plugin.json`, and wire its `skills/` and
`mcp.json` into the host. No per-business branching — same code path works
for `tm-dagu-adapt`, `agenthandoff`, or any future `agent-plugins.org`
package.
> Forked from [zoahdev/dsh-plugin-template](https://github.com/zoahdev/dsh-plugin-template):
> carries the template's runtime peer-version guard, the bundled `cordis.patch.yml`,
> the unit + packaged integration + DSH-boot smoke posture, and the bilingual README.
>
> Tested with: `dsh` 0.1.0-rc.6 · Node 24 · pnpm 11
## What's inside
```text
├── package.json # dsh.bundle manifest + build scripts (prepare = git-install build)
├── cordis.patch.yml # plugin row: id, package name, default config
├── src/
│ ├── index.ts # plugin entry: name / inject / Config / apply + bridge_summary tool + peer guard
│ ├── version.ts # dependency-free caret-range matcher used by the runtime guard
│ ├── discover.ts # plugin.json + skills/ + mcp.json discovery
│ ├── expand.ts # ${VAR} placeholder expansion
│ ├── mcp.ts # Agent Plugin MCP + standalone mcp.json registration
│ ├── skills.ts # SKILL.md discovery and host-skills registration
│ └── types.ts # shared wire types
├── tests/
│ ├── index.spec.ts # plugin registration, runBridge end-to-end (mocked ctx)
│ ├── version.spec.ts # prerelease range behavior matrix
│ ├── discover.spec.ts # plugin.json scanning, dedup
│ ├── expand.spec.ts # ${VAR} expansion rules
│ └── skills.spec.ts # SKILL.md → host skills service mapping
├── scripts/
│ ├── integration-test.mjs # installs the PACKED tarball → real apply() → real tool execution
│ ├── local-skill.mjs # loads the packed plugin into a real cordis context, registers a real Agent Plugin (default: tm-dagu-adapt), asserts bridge_summary
│ └── dsh-smoke.sh # fresh DSH profile install + config check + web boot (bounded retry)
├── .github/workflows/ci.yml # doctor → test → pack → integration → DSH boot smoke (windows-latest)
└── README.md # bilingual
```
## What the bridge actually does
1. On startup, `apply()` runs `assertPeerCompatible()` and refuses to load if
the resolved `@deepseek-ai/dsh-tools` does not satisfy `^0.1.0-rc.6`. This
turns pnpm's silent older-RC linking into a loud, actionable error.
2. It then calls `discoverPlugins(pluginRoots, pluginsDirs)`. Each
candidate directory must contain a `plugin.json` with a `name` field;
optional `skills/` and `mcp.json` are picked up if present.
3. For every discovered plugin it registers each `skills/<folder>/SKILL.md`
into the host `skills` service (frontmatter is parsed with a tiny
dependency-free parser). If `loadPluginMcp` is on it also registers the
plugin's `mcp.json` against the host MCP client.
4. It then iterates `mcpJsonPaths` and registers every standalone mcp.json
(typical: `~/.cursor/mcp.json`) the same way — same wire format, same
`${VAR}` expansion semantics.
5. It registers a `bridge_summary` tool so the agent can introspect what was
actually loaded.
## Two kinds of `mcp.json`
Agent Plugins 1.0 bundles an `mcp.json` per package. Those often use
`${SOME_ENV}` placeholders that only resolve when the host has the right
secrets — until then they fail with `empty command after env expansion`.
The bridge exposes two knobs:
- `loadPluginMcp` (default `true`) — load the per-plugin `mcp.json`.
- `mcpJsonPaths` — extra standalone `mcp.json` files outside any package.
Typical: `~/.cursor/mcp.json`, which is the user-level MCP config and
does not depend on plugin env vars.
Set `loadPluginMcp: false` while the env vars are missing and the bridge
will still pick up the standalone file.
## Configuration
`cordis.patch.yml` ships these defaults — override them in your profile
config:
```yaml
- id: dsh-agent-plugin-bridge
name: dsh-agent-plugin-bridge
config:
pluginRoots: [] # explicit Agent Plugin package roots
pluginsDirs: # directories whose children are scanned
- '~/.cursor/plugins/local'
mcpJsonPaths: # extra standalone mcp.json files
- '~/.cursor/mcp.json'
loadPluginMcp: false # load each plugin's own mcp.json (default off, plugin mcp.json usually needs host env vars)
failOnMcpError: false # keep going when an MCP server fails to start
skillProviderLabel: agent-plugin
```
Use `~` in paths; the bridge expands it against `homedir()`.
## Dependency strategy (read this)
- **Tested with**: `@deepseek-ai/dsh-tools` **0.1.0-rc.6** and `@deepseek-ai/cordis` **^4.0.1**.
- `peerDependencies` declares `"@deepseek-ai/dsh-tools": "^0.1.0-rc.6"`. This is a **caret range, not a pin**:
- It currently matches `0.1.0-rc.6`, later RCs of `0.1.x` (`rc.7`, `rc.10`, ...), and `0.1.0` stable once published.
- It does **not** match `0.1.0-rc.5`/older RCs, nor the `0.0.1-rc.*` train.
- `devDependencies` uses the same range; the committed `pnpm-lock.yaml` pins the exact tested version for development and CI.
- **Empirically verified with pnpm 11**: if the host already contains an older RC (e.g. `0.1.0-rc.3`), pnpm's default config links that older version into the plugin's peer slot with only a generic warning — **no error, no auto-upgrade**. npm fails loudly with `ERESOLVE` instead. Neither tool auto-upgrades the host.
The plugin refuses to load when the resolved `@deepseek-ai/dsh-tools` does
not satisfy `^0.1.0-rc.6` (runtime guard in `apply()`, backed by
`src/version.ts`). A silent mismatch becomes a clear, actionable error.
## Use it
The bridge is bundled for the standard `dsh plugin add` flow:
```sh
pnpm install
pnpm build
pnpm test
pnpm pack
dsh plugin --profile web add ./dsh-agent-plugin-bridge-0.1.0.tgz
dsh web --port 4099
```
Then ask the agent: "Use the bridge_summary tool to tell me what was loaded."
## CI
`.github/workflows/ci.yml` mirrors the template pipeline:
1. clean checkout
2. `pnpm install --frozen-lockfile`
3. `pnpm typecheck`
4. `pnpm run build`
5. `pnpm test` (unit)
6. `pnpm pack`
7. **packaged integration + real tool invocation** — `scripts/integration-test.mjs` installs the actual tarball into a fresh project, loads the installed bundle, registers `bridge_summary` through the real `apply()` / `ctx.tools.register` path, executes the real handler, and asserts the canonical result.
8. `dsh-smoke` job (windows-latest): `scripts/dsh-smoke.sh` installs the tarball into a brand-new `DSH_HOME`, verifies the plugin row in `--dump-config`, boots `dsh web` with a 30s bounded retry, and cleans up the background process.
For local end-to-end without `dsh web` (when C: is full or you cannot install `@deepseek-ai/dsh`):
```sh
pnpm pack
node scripts/local-skill.mjs D:/AgentRepo/GitRepo/GitRepo/tm-dagu-adapt
```
This loads the packed tarball into a real cordis `Context`, registers the first `plugin.json` it finds, registers its `SKILL.md` into the host `skills` service, and asserts `bridge_summary.execute()` returns the right counts.
## Publishing checklist
- [ ] `pnpm typecheck` and `pnpm build` pass
- [ ] `pnpm test` passes
- [ ] `pnpm pack` produces a tarball
- [ ] **packaged plugin loads in a fresh profile** (integration + smoke scripts pass)
- [ ] **`bridge_summary` runtime invocation passes with an asserted result** (integration script)
- [ ] README bilingual, with install, config, examples, and troubleshooting
- [ ] Repo topic: `dsh-plugin`
- [ ] Tag a release (e.g. `v0.1.0`) with the packed tarball
- [ ] Optional: `pnpm publish` to npm
## Troubleshooting
### npm: `ERESOLVE` peer dependency conflict
The host already has an older RC that does not satisfy `^0.1.0-rc.6`.
1. Upgrade the host to the tested version or newer:
```sh
pnpm dlx @deepseek-ai/dsh --version # must print 0.1.0-rc.6 or later
```
2. Reinstall the plugin so it links against the upgraded host:
```sh
pnpm dlx @deepseek-ai/dsh plugin --profile web add <this-plugin>
```
3. Do **not** reach for `--legacy-peer-deps` to silence the error — the plugin's runtime guard will refuse to load if an incompatible version is linked anyway.
### pnpm: install succeeds but the plugin later fails to load
pnpm's default config can silently link an older RC into the plugin's peer slot. The plugin then refuses to load with:
```text
dsh-agent-plugin-bridge: resolved @deepseek-ai/dsh-tools 0.1.0-rc.3, but this plugin is tested with ^0.1.0-rc.6. ...
```
1. Upgrade the host to `0.1.0-rc.6` or later and reinstall.
2. Optional hardening: enable `strict-peer-dependencies=true` in your project/profile `.npmrc`.
### `mcp ${server}: empty command after env expansion`
A plugin's `mcp.json` uses a `${SOME_ENV}` placeholder that the host does not have. Two options:
- Set the env vars so the bridge can expand the command.
- Or temporarily set `loadPluginMcp: false` in `cordis.patch.yml` and rely on a standalone `mcp.json` (e.g. `~/.cursor/mcp.json`) instead.
### Agent skills don't show up
- Make sure the package actually has `plugin.json` with a `name` field — the bridge silently skips invalid manifests.
- Check `pluginsDirs` and `pluginRoots`; paths starting with `~` are expanded against `homedir()`.
- The host must expose the `skills` service (this plugin declares `inject = ['skills']`).
### You verified a newer RC and want to move the plugin forward
Bump `TESTED_PEER_RANGE` in `src/index.ts`, update `package.json` (peer + dev), regenerate `pnpm-lock.yaml` (`pnpm install`), and update the "Tested with" line in this README — all four together.
## License
MIT
---
## 中文
**dsh-agent-plugin-bridge** 是通用 Agent Plugins 1.0 → DeepSeek Harness 桥接插件:把它装进任何 DSH profile,
它就会扫描一组 plugin 根目录与 plugin 目录,挑出所有带 `plugin.json` 的包,把 `skills/` 和 `mcp.json`
挂到宿主上。不按业务分叉——`tm-dagu-adapt`、`agenthandoff` 或任何未来符合 `agent-plugins.org` 规范的包都走同一条代码路径。
> Fork 自 [zoahdev/dsh-plugin-template](https://github.com/zoahdev/dsh-plugin-template):
> 沿用了模板的运行时 peer 版本守卫、捆绑的 `cordis.patch.yml`、单元 + 打包集成 + DSH 启动冒烟流程、双语 README。
>
> 已验证版本:`dsh` 0.1.0-rc.6 · Node 24 · pnpm 11
## 包含内容
```text
├── package.json # dsh.bundle 清单 + 构建脚本(prepare 支持 git 安装)
├── cordis.patch.yml # 插件行:id、包名、默认 config
├── src/
│ ├── index.ts # 插件入口 + bridge_summary 工具 + 运行时版本守卫
│ ├── version.ts # 无依赖的 caret 范围匹配器(守卫使用)
│ ├── discover.ts # plugin.json + skills/ + mcp.json 发现
│ ├── expand.ts # ${VAR} 占位符展开
│ ├── mcp.ts # Agent Plugin MCP 与独立 mcp.json 注册
│ ├── skills.ts # SKILL.md 发现并写入宿主 skills 服务
│ └── types.ts # 共享线协议类型
├── tests/ # vitest:注册、行为、取消、解析
├── scripts/
│ ├── integration-test.mjs # 安装打包产物 → apply() → 执行真实 bridge_summary → 断言
│ ├── local-skill.mjs # 真 cordis 上下文里加载打包插件,加载真实 Agent Plugin(默认 tm-dagu-adapt),断言 bridge_summary
│ └── dsh-smoke.sh # 全新 DSH profile 安装 + 配置校验 + web 启动(限时重试)
├── .github/workflows/ci.yml # doctor → test → pack → integration → DSH 启动冒烟(windows-latest)
└── README.md # 双语
```
## 桥接到底做了什么
1. 启动时 `apply()` 先跑 `assertPeerCompatible()`,解析到的 `@deepseek-ai/dsh-tools`
不满足 `^0.1.0-rc.6` 就直接拒绝加载。把 pnpm 静默链接老 RC 变成响亮、可操作的报错。
2. 调 `discoverPlugins(pluginRoots, pluginsDirs)`。每个候选目录必须含 `plugin.json` 与 `name` 字段;
有 `skills/` 与 `mcp.json` 就一并收下。
3. 对每个发现的插件,把 `skills/<folder>/SKILL.md` 注册进宿主 `skills` 服务
(frontmatter 用一个免依赖的小解析器处理)。`loadPluginMcp` 为开时再把它自己的 `mcp.json` 注册到宿主 MCP 客户端。
4. 遍历 `mcpJsonPaths`,把每个独立的 `mcp.json`(典型:`~/.cursor/mcp.json`)也按同样规则注册——同样的线协议、同样的 `${VAR}` 展开语义。
5. 注册一个 `bridge_summary` 工具,agent 可以随时回头查"这一轮到底加载了什么"。
## 两类 `mcp.json`
Agent Plugins 1.0 的包内 `mcp.json` 经常使用 `${SOME_ENV}` 占位符,需要宿主里准备好对应变量;
在变量到位之前,启动时会报 `empty command after env expansion`。桥接给了两个旋钮:
- `loadPluginMcp`(默认 `true`)——加载每个插件自己的 `mcp.json`。
- `mcpJsonPaths`——额外、独立于任何包的 `mcp.json` 文件。典型:`~/.cursor/mcp.json`,是用户级 MCP 配置,不依赖插件环境变量。
环境变量没到位的时候,把 `loadPluginMcp: false`,桥接仍然能加载独立的那份。
## 配置
`cordis.patch.yml` 自带默认值,需要在 profile config 里覆盖就覆盖:
```yaml
- id: dsh-agent-plugin-bridge
name: dsh-agent-plugin-bridge
config:
pluginRoots: [] # 显式 Agent Plugin 包根
pluginsDirs: # 子目录会被扫描
- '~/.cursor/plugins/local'
mcpJsonPaths: # 额外的独立 mcp.json 文件
- '~/.cursor/mcp.json'
loadPluginMcp: false # 是否加载插件自己的 mcp.json(默认关:插件 mcp.json 经常依赖宿主环境变量)
failOnMcpError: false # MCP 启动失败时是否中止整桥
skillProviderLabel: agent-plugin
```
路径里的 `~` 会按 `homedir()` 展开。
## 依赖策略(请读这一段)
- **已验证**:`@deepseek-ai/dsh-tools` **0.1.0-rc.6**、`@deepseek-ai/cordis` **^4.0.1**。
- `peerDependencies` 声明 `"@deepseek-ai/dsh-tools": "^0.1.0-rc.6"`。这是 **caret 范围,不是 pin**:
- 目前匹配 `0.1.0-rc.6`、后续 `0.1.x` 的 RC(`rc.7`、`rc.10`…),以及未来发布的 `0.1.0` 稳定版。
- **不匹配** `0.1.0-rc.5` 及更早 RC,也不匹配 `0.0.1-rc.*` 版本线。
- `devDependencies` 使用同一范围;提交的 `pnpm-lock.yaml` 把开发与 CI 固定到已验证的确切版本。
- **pnpm 11 实测**:宿主已存在旧 RC(如 `0.1.0-rc.3`)时,pnpm 默认配置会把旧版本链进插件的 peer 槽,只给一条泛泛的警告。
npm 则会以 `ERESOLVE` 响亮失败。两个工具都不会自动帮你升级宿主。
插件在 `apply()` 里加了**运行时版本守卫**(`src/version.ts` 支撑),把静默不兼容变成清晰、可操作的报错。
## 使用
按 `dsh plugin add` 标准流程安装:
```sh
pnpm install
pnpm build
pnpm test
pnpm pack
dsh plugin --profile web add ./dsh-agent-plugin-bridge-0.1.0.tgz
dsh web --port 4099
```
然后让 agent:"用 bridge_summary 工具告诉我加载了什么。"
## CI
`.github/workflows/ci.yml` 复刻模板流水线:
1. clean checkout
2. `pnpm install --frozen-lockfile`
3. `pnpm typecheck`
4. `pnpm run build`
5. `pnpm test`(单元)
6. `pnpm pack`
7. **打包产物集成 + 真实工具调用**——`scripts/integration-test.mjs` 把实际 tarball 装进全新项目,加载已安装产物,
通过真实的 `apply()` / `ctx.tools.register` 注册 `bridge_summary`,执行真实 handler,断言返回结果。
8. `dsh-smoke` job(windows-latest):`scripts/dsh-smoke.sh` 在全新 `DSH_HOME` 安装 tarball,
校验 `--dump-config` 里的插件行,30 秒限时重试启动 `dsh web`,并清理后台进程。
本地端到端(不需要 `dsh web`、C: 盘满或者装不了 `@deepseek-ai/dsh` 时用):
```sh
pnpm pack
node scripts/local-skill.mjs D:/AgentRepo/GitRepo/GitRepo/tm-dagu-adapt
```
——把打包好的 tarball 装进一个真 cordis `Context`,加载第一个 `plugin.json`,把 `SKILL.md` 写入宿主 `skills` 服务,断言 `bridge_summary.execute()` 返回的计数。
## 发布清单
- [ ] `pnpm typecheck` 与 `pnpm build` 通过
- [ ] `pnpm test` 通过
- [ ] `pnpm pack` 产出 tarball
- [ ] **打包产物能在全新 profile 加载**(集成 + 冒烟脚本通过)
- [ ] **`bridge_summary` 运行时调用通过并有明确断言**(集成脚本)
- [ ] README 双语:安装、配置、示例、故障排查
- [ ] 仓库话题 `dsh-plugin`
- [ ] 打 Release(如 `v0.1.0`)并附 tarball
- [ ] 可选:`pnpm publish` 发 npm
## 故障排查
### npm:`ERESOLVE` peer 依赖冲突
宿主已有不满足 `^0.1.0-rc.6` 的旧 RC。
1. 把宿主升到已验证版本或更新:
```sh
pnpm dlx @deepseek-ai/dsh --version # 必须打印 0.1.0-rc.6 或更新
```
2. 重新安装插件,让它链接到升级后的宿主:
```sh
pnpm dlx @deepseek-ai/dsh plugin --profile web add <本插件>
```
3. **不要**用 `--legacy-peer-deps` 压掉错误——压掉以后运行时守卫照样会在版本不对时拒绝加载。
### pnpm:安装成功但插件加载失败
pnpm 默认配置可能把旧 RC 静默链进插件的 peer 槽。插件随后拒绝加载,报错形如:
```text
dsh-agent-plugin-bridge: resolved @deepseek-ai/dsh-tools 0.1.0-rc.3, but this plugin is tested with ^0.1.0-rc.6. ...
```
1. 把宿主升到 `0.1.0-rc.6` 或更新,然后重装。
2. 可选加固:在项目/profile 的 `.npmrc` 里加 `strict-peer-dependencies=true`,让 pnpm 响亮失败。
### `mcp ${server}: empty command after env expansion`
插件 `mcp.json` 用了 `${SOME_ENV}` 占位符,宿主里没设环境变量。两种修法:
- 把环境变量补齐,让桥接能正确展开命令。
- 临时在 `cordis.patch.yml` 里把 `loadPluginMcp: false`,并依赖独立的 `mcp.json`(如 `~/.cursor/mcp.json`)。
### Agent skills 没出现
- 先确认包确实有 `plugin.json` 且带 `name` 字段——桥接对非法清单会静默跳过。
- 检查 `pluginsDirs` 与 `pluginRoots`;以 `~` 开头的路径会按 `homedir()` 展开。
- 宿主必须暴露 `skills` 服务(本插件声明 `inject = ['skills']`)。
### 你验证了更新的 RC,想把插件推进
同步改四处:`src/index.ts` 的 `TESTED_PEER_RANGE`、`package.json`(peer + dev)、
`pnpm-lock.yaml`(重新 `pnpm install`)、README 的"已验证版本"行。
## 许可证
MIT
Install
dsh plugin --profile web add github:dubeno/dsh-agent-plugin-bridge
Profile: web
With the hub plugin installed, ask your agent to install it by name — it resolves the same plan shown here.
dsh plugin --profile web add github:stvlynn/dsh.fish#path:packages/dsh-plugin-hub
install dsh-agent-plugin-bridge from the hub
- This package builds from source on install. pnpm will ask you to allow its build script — that is permission to run the package’s code on your machine, outside the agent sandbox. Only allow sources you trust.
- This source has no pinned commit, so a later push upstream changes what installs. Prefer pinning a commit.