@omdp/dsh-connector 0.3.3 → 0.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +50 -4
  2. package/index.js +57 -1
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @omdp/dsh-connector
2
2
 
3
- **MCP 服务器 + 用户 Skills + 魔搭市场浏览三合一设置页**(`v0.3.3`)。适合需要在 DSH 里频繁增删改 MCP server / skills、又不想手改 `cordis.patch.yml` 的用户。
3
+ **MCP 服务器 + 用户 Skills + 魔搭市场浏览三合一设置页**(`v0.3.4`)。适合需要在 DSH 里频繁增删改 MCP server / skills、又不想手改 `cordis.patch.yml` 的用户。
4
4
 
5
5
  ## Requirements
6
6
 
@@ -8,6 +8,29 @@
8
8
  - Node.js `^22.19` 或 `>=24`
9
9
  - `@deepseek-ai/schemastery` `3.18.1` / `3.18.2` / `3.18.4`(peer 枚举,供工具过滤的 Config 声明用;无则过滤静默全放行)
10
10
  - 已实测 DSH **0.1.5-rc.1**(源码级核查,2026-09-10)、**0.1.5-rc.2** 与 **0.1.6-alpha.1**(源码级 + 运行时冒烟:`/connector/api/*` 正常服务、设置页渲染,2026-09-15,见 `docs/plugin-compatibility.md`)、**0.1.7-rc.1**(0.3.3 适配:修掉 import 期崩溃 + 迁移到 settings 托管配置,2026-09-24)
11
+ - **`@deepseek-ai/dsh` peer 声明 `0.1.7-rc.1`**(0.3.4 起,逐版本枚举;规范见 `AGENTS.md` 规范 3)
12
+
13
+ ## 兼容性门禁(0.3.4 起声明)
14
+
15
+ 0.3.4 起本插件在 `peerDependencies` 里**显式声明支持的 DSH 版本**:
16
+
17
+ ```json
18
+ "peerDependencies": {
19
+ "@deepseek-ai/dsh": "0.1.7-rc.1"
20
+ }
21
+ ```
22
+
23
+ 这条声明由 DSH 自己的 **`evaluatePluginCompatibility()`**(`dsh-app-boot` 的公开导出,是一个 Inspector 可查的正式机制、不是本插件自造的约定)在**安装时**与**每次启动时**校验,语义是「声明版本 = 我实测过的版本」:
24
+
25
+ | 运行中的 DSH | 行为 |
26
+ |---|---|
27
+ | **`0.1.7-rc.1`**(本次实测) | ✅ `evaluatePluginCompatibility()` 返回 `undefined` ⇒ 正常加载,与 0.3.3 行为一致 |
28
+ | **`0.1.7` 及更新的、未实测版本** | ⛔ 门禁拦下:启动时**整个 bundle 被跳过**(stderr 打 `skipping profile bundle "@omdp/dsh-connector"`),安装时该行被置灰 |
29
+ | **`≤0.1.6` 及 `0.1.7-alpha.x`** | ➖ **不受影响**:那些版本里**根本没有这个门禁**(经解包 npm tarball 逐版核对,`0.1.5-rc.2`/`0.1.5-rc.3`/`0.1.6-alpha.1`/`0.1.6-alpha.2`/`0.1.7-alpha.1`/`0.1.7-alpha.2` 的 `dsh-app-boot` 里 `evaluatePluginCompatibility` 出现 **0 次**,只有 `0.1.7-rc.1` 出现 4 次),旧运行时读到这条 peer 只是「不认识的声明」,照常加载 |
30
+
31
+ **为什么故意只写 `0.1.7-rc.1`(而不是写老版本)**:老版本运行时压根不执行这个检查,写进去纯属装饰、无法被验证;而**未实测的新版本必须被拦住**——这正是 `AGENTS.md` 规范 3 的要求(`peerDependencies` 只精确枚举实测过的版本,禁止开放范围,未核查的版本宁可报 unmet peer 也不得预先放行)。门禁被触发时是**优雅跳过**(插件不加载、DSH 照常启动),符合本插件「硬保证 = DSH 不会因插件崩溃」的抗崩溃设计。
32
+
33
+ > 升级 DSH 后若设置页突然看不到 Connector 标签,先看启动日志有没有 `skipping profile bundle`——那就是门禁在提醒你「该插件尚未针对这个 DSH 版本做兼容核查」,核查通过后把该版本追加进上面的枚举并同步更新 `docs/plugin-compatibility.md`。
11
34
 
12
35
  ## Overview
13
36
 
@@ -82,7 +105,7 @@ pnpm install --lockfile-only --offline # 按 link 依赖重写 lockfile
82
105
 
83
106
  ```jsonc
84
107
  "dependencies": {
85
- "@omdp/dsh-connector": "^0.3.0"
108
+ "@omdp/dsh-connector": "^0.3.4"
86
109
  }
87
110
  ```
88
111
 
@@ -183,6 +206,8 @@ pnpm remove @omdp/dsh-connector
183
206
  |---|---|
184
207
  | 设置页看不到 Connector 标签 | bundle 未挂载:确认 `dsh.profile.bundles` 含 `@omdp/dsh-connector`,重启 dsh;仍无则检查 client.js 尾部 `exports.inject = ['slots']` 是否在(缺了会静默丢注册) |
185
208
  | 工具过滤不生效(模型仍能看到/调用) | 过滤规则只对**新会话**生效(当前会话的 schema 已下发);确认规则落盘位置正确——0.1.7+ 看 profile 条目 `connector` 的 `config.toolFilters`,rc.x 看 `~/.dsh/settings.yaml` 的 `connector.toolFilters`;再确认 serverName 拼写与 patch 一致 |
209
+ | 升级 0.1.7 后勾好的工具过滤变回全量放行 | 0.1.7 的配置迁移会静默漏掉未声明 volatile `Config` 的段,规则被留在 `~/.dsh/settings.yaml.imported` 里;0.3.4 的 `ensureLegacyFiltersMigration()` 会在首次请求时自动搬回(日志 `migrated toolFilters from settings.yaml.imported`)。**不要删 `settings.yaml.imported`**——它是遗留配置的唯一副本。若仍未恢复,用 `PUT /connector/api/mcp/filters` 手写一次即可 |
210
+ | 升级 DSH 后设置页整页丢失 Connector 标签、`/connector/api/*` 全 404 | 先查启动日志有无 `skipping profile bundle "@omdp/dsh-connector"`——这是 0.1.7 起的**兼容性门禁**拦下了尚未核查的 DSH 版本(见「## 兼容性门禁」)。核查通过后把该版本追加进 `peerDependencies["@deepseek-ai/dsh"]` 枚举 |
186
211
  | 保存 MCP 被拒(HTTP 400) | 配置不合法(transport/serverName/command/url 校验失败),按提示修正——插件不会写入坏配置 |
187
212
  | MCP server 保存后不生效 | 需**重启 dsh**(`dsh-mcp-client` 静态加载) |
188
213
  | `/connector/api/*` 404 | client/host 边界异常:确认插件 host 半边已加载(重启),浏览器强刷缓存 |
@@ -231,7 +256,7 @@ MIT License。安全问题请通过 GitHub Issues 私密报告(https://github.
231
256
  |---|---|---|
232
257
  | `profiles/web/cordis.patch.yml` | **读写** | MCP 块的结构化编辑(保留 `!!js`/env 原样);0.1.7+ 工具过滤规则也由 DSH settings 服务写回本行 `config.toolFilters` |
233
258
  | `~/.dsh/skills/**/SKILL.md` | **读写** | 用户技能文件的查看/编辑/删除/新建;「记录来源」会写 `source`/`sourceUpdated` frontmatter |
234
- | `~/.dsh/settings.yaml` 等 | rc.x 经 settings 服务读 | 0.1.7+ 不再直接读写该文件(DSH 已改为 profile 条目托管) |
259
+ | `~/.dsh/settings.yaml` 等 | rc.x 经 settings 服务读;0.1.7+ **只读** `settings.yaml.imported` 做一次性救援 | 0.1.7+ 不再直接读写活动配置(DSH 已改为 profile 条目托管);`.imported` 是遗留配置唯一副本,**永不删除** |
235
260
  | HTTP `/connector/api/*` | 本机监听 | 与 DSH GUI 同源,无额外鉴权 |
236
261
  | 魔搭 `modelscope.cn/openapi/v1` | **只读外部** | 市场浏览代理(匿名);结果仅存进程内存(30 分钟 TTL),**不写文件、不落盘** |
237
262
  | 环境变量 | 只读引用 | 只读 `process.env.*`,不持久化 |
@@ -288,12 +313,33 @@ TypeError: createRequire.resolve.paths is not a function or its return value is
288
313
  | 魔搭市场不可达 | ✅ 市场接口报 502,本地 MCP/skill 管理不受影响 |
289
314
 
290
315
  **最后验证**:DSH `0.1.7-rc.1`(2026-09-24,本轮修复:模块 import 期零抛错,`node --check` + 实际
291
- `import()` 冒烟通过;工具过滤读写改用 0.1.7 的 `Config`+`.volatile()`+`settings.replace()`)。历史:
316
+ `import()` 冒烟通过;工具过滤读写改用 0.1.7 的 `Config`+`.volatile()`+`settings.replace()`;
317
+ 0.3.4 追加 `@deepseek-ai/dsh` peer 声明并用 DSH 真实 `evaluatePluginCompatibility()` 验证门禁行为、
318
+ 追加遗留 `toolFilters` 一次性救援)。历史:
292
319
  DSH `0.1.0-rc.8`(2026-08-20);0.2.0 市场功能以 `node --check` + 真实 HTTP 集成测试通过(11 项:
293
320
  skills/mcp 列表与详情、证书/Hosted 标识、安装命令、记录来源回写、更新判定),未改动 DSH 实例。
294
321
 
295
322
  ## 变更记录
296
323
 
324
+ - **0.3.4**(2026-09-24):**声明 DSH 版本支持 + 修复工具过滤被静默清空**。
325
+ 1. **`@deepseek-ai/dsh` peer 声明 `0.1.7-rc.1`**(逐版本枚举,语义见上方「## 兼容性门禁」)。
326
+ 用 DSH 真实的 `evaluatePluginCompatibility()` 逐一验证:`0.1.7-rc.1` → 正常加载;
327
+ `0.1.7-alpha.2` / `0.1.6-alpha.1` / `0.1.5-rc.3` / `0.1.8-rc.1` → 被门禁拦下(前者是老运行时
328
+ 无门禁、后者是未实测的新版本,均符合规范 3)。
329
+ 2. **修复工具过滤丢失(根因:DSH 0.1.7 的配置迁移是「全有或全无」)** —— 0.1.7 首次启动会把
330
+ `<DSH_HOME>/settings.yaml` 改名为 `settings.yaml.imported` 并逐段导入 profile 条目;该导入
331
+ 对**没有 volatile `Config` 声明的段**会**静默跳过**(只往 stderr 打一行 `settings: section …
332
+ was not imported`)。本插件 0.3.3 才刚引入 `Config`,所以用户机器上 `connector.toolFilters`
333
+ 被落在 `settings.yaml.imported` 里没搬过来 ⇒ 过滤规则读成空 ⇒ **全量放行**(表现为
334
+ 「设置页里勾的过滤没了」)。
335
+ 修复:新增 `ensureLegacyFiltersMigration()`,在**路由处理前**(`apply()` 期间 `settings.replace()`
336
+ 不可用,因为该条目 fiber 尚未进入 ACTIVE 状态)检查一次——若活的 `config.toolFilters` 为空、
337
+ 而 `settings.yaml.imported` 里还留着 `connector.toolFilters`,就把它写回本插件的 profile 条目
338
+ 并打一行 `migrated toolFilters from settings.yaml.imported`。失败时复位重试标记,下一次请求再试。
339
+ 一次性且幂等:已有非空过滤规则时**不覆盖用户当前设置**。
340
+ 3. `settings.yaml.imported` **只读、绝不删除**——它是遗留配置的唯一副本(0.1.7 迁移后就地改名,
341
+ 原始 `settings.yaml` 已不存在;DSH 内置导入器见到 `.imported` 不会再跑)。
342
+
297
343
  - **0.3.3**(2026-09-24):**适配 DSH 0.1.7-rc.1**。
298
344
  1. **修复 import 期崩溃**:`jsdom` 由顶层静态 import 改为按需 `await import()`(原因见上方
299
345
  「为什么 jsdom 必须懒加载」)——0.3.2 在 0.1.7 上整个插件 import 失败、设置页永远显示
package/index.js CHANGED
@@ -93,6 +93,8 @@ export const Config = (typeof _toolFiltersField.volatile === 'function')
93
93
  let _filterRef = null // 0.1.7+: config.toolFilters 这个活 Ref
94
94
  let _legacyScope = null // rc.x: settings.register(...) 返回的 scope
95
95
  let _legacySvc = null // rc.x: settings 服务(读用 get,写用 update)
96
+ let _settingsSvc = null // 0.1.7+: ctx.settings(迁移写回用,见 ensureLegacyFiltersMigration)
97
+ let _migrationAttempted = false
96
98
  function readToolFilters() {
97
99
  try {
98
100
  // 优先 0.1.7+ 的活 Ref(.get() 每次返回最新值)
@@ -117,6 +119,54 @@ function normalizeToolFilters(filters) {
117
119
  }
118
120
  return out
119
121
  }
122
+
123
+ /* ── 0.1.7 迁移遗留救援 ──────────────────────────────────────────────────────
124
+ * DSH 0.1.7 启动时把 <home>/settings.yaml 改名为 settings.yaml.imported,再把每个
125
+ * section 当作对应 entry 的 config override 调 settings.update(ns, values) 写回。
126
+ * 而 update() 走 write(),要求该 entry 的 Config 含 volatile 字段,否则抛
127
+ * 「Config field ... is not volatile」,只打一条 warn 就跳过——配置静默留在
128
+ * .imported 里,插件再也看不到它(UI 显示「未配置 = 全量放行」,用户以为改动被吞)。
129
+ *
130
+ * 本插件 0.3.2 及更早没有 volatile 的 toolFilters,升级 0.1.7 时用户的过滤规则
131
+ * 就是这样丢的。0.3.3 补上了 volatile Config(写入通道已通),但已经丢过一次的
132
+ * 数据不会自己回来,所以这里做一次性救援:把滞留在 .imported 的 connector 节
133
+ * 搬进 profile entry 的 config(即 cordis.patch.yml 的 connector 行)。
134
+ *
135
+ * 时序:settings.replace() 要求该 entry 的 fiber 处于 ACTIVE(2)——apply() 期间
136
+ * 还没到,所以这里只在惰性调用(首次读 filters 的路由)里跑,绝不放进 apply()。
137
+ * 语义:只搬一次;本插件已有配置时不动(避免覆盖用户后来的修改);失败不抛错
138
+ * (GET 必须可用),留给下一次请求重试。
139
+ */
140
+ async function ensureLegacyFiltersMigration() {
141
+ if (_migrationAttempted) return
142
+ _migrationAttempted = true
143
+ try {
144
+ if (!_filterRef || !_settingsSvc || typeof _settingsSvc.replace !== 'function') return
145
+ // 已有配置 → 用户在新后端上配过了,不要覆盖。
146
+ const existing = normalizeToolFilters(_filterRef.get())
147
+ if (Object.keys(existing).length > 0) return
148
+ // 读遗留文档:不存在 = 全新安装或已被内置导入器处理过,无事可做。
149
+ let text
150
+ try {
151
+ text = await readFile(join(resolveHome(), 'settings.yaml.imported'), 'utf8')
152
+ } catch {
153
+ return
154
+ }
155
+ let doc
156
+ try { doc = parseYaml(text) } catch { return }
157
+ if (!doc || typeof doc !== 'object' || Array.isArray(doc)) return
158
+ const block = doc[CONNECTOR_SETTINGS_NS]
159
+ const migrated = normalizeToolFilters(block && block.toolFilters)
160
+ if (Object.keys(migrated).length === 0) return
161
+ await _settingsSvc.replace(CONNECTOR_ENTRY_ID, { toolFilters: migrated })
162
+ console.info('[dsh-connector] migrated toolFilters from settings.yaml.imported:', Object.keys(migrated).join(', '))
163
+ } catch (error) {
164
+ // 写失败(entry 尚未 ACTIVE、revision 冲突等):清标记让下一次请求重试,
165
+ // 否则一次过早的调用会永久放弃救援。
166
+ _migrationAttempted = false
167
+ console.warn('[dsh-connector] toolFilters migration skipped:', error?.message ?? error)
168
+ }
169
+ }
120
170
  // 公开名 `mcp__<server>__<raw>` 反解回 (server, raw):注意 serverName 本身可含
121
171
  // 下划线,所以按 `mcp__` 前缀 + __ 分段取“第一段”为 server,余下 join 回 raw。
122
172
  function splitPublicName(publicName) {
@@ -987,7 +1037,8 @@ export function apply(ctx, config) {
987
1037
  const field = config && config.toolFilters
988
1038
  if (field && typeof field.get === 'function') {
989
1039
  _filterRef = field
990
- ctx.effect(() => () => { _filterRef = null }, 'connector: release tool filter ref')
1040
+ _settingsSvc = ctx.get('settings') || null
1041
+ ctx.effect(() => () => { _filterRef = null; _settingsSvc = null }, 'connector: release tool filter ref')
991
1042
  } else {
992
1043
  // ── rc.x 老后端:注册 settings namespace(本插件自己的两段式配置)──
993
1044
  const settings = ctx.get('settings')
@@ -1039,6 +1090,11 @@ export function apply(ctx, config) {
1039
1090
  try {
1040
1091
  if (!path.startsWith(API_PREFIX)) { res.writeHead(404); res.end(); return }
1041
1092
 
1093
+ // 首次请求时救援 0.1.7 迁移遗留的 toolFilters(见 ensureLegacyFiltersMigration)。
1094
+ // 放在这里而不是 apply():settings.replace() 要求本 entry 已 ACTIVE。
1095
+ // 成功后 _migrationAttempted 短路,后续请求零开销。
1096
+ await ensureLegacyFiltersMigration()
1097
+
1042
1098
  // GET /api/mcp — list server entries (with preserve buckets)
1043
1099
  if (req.method === 'GET' && path === API_PREFIX + '/mcp') {
1044
1100
  let text
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omdp/dsh-connector",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "description": "Unified DeepSeek Harness connector: edit MCP servers (cordis.patch.yml), user skills (~/.dsh/skills) and browse the ModelScope MCP/Skills marketplaces from one Web UI settings page.",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -46,6 +46,7 @@
46
46
  "jsdom": "^24.1.3"
47
47
  },
48
48
  "peerDependencies": {
49
+ "@deepseek-ai/dsh": "0.1.7-rc.1",
49
50
  "@deepseek-ai/schemastery": "3.18.1 || 3.18.2 || 3.18.4"
50
51
  }
51
52
  }