@omdp/dsh-connector 0.3.4 → 0.3.6

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.
package/README.md CHANGED
@@ -1,14 +1,14 @@
1
1
  # @omdp/dsh-connector
2
2
 
3
- **MCP 服务器 + 用户 Skills + 魔搭市场浏览三合一设置页**(`v0.3.4`)。适合需要在 DSH 里频繁增删改 MCP server / skills、又不想手改 `cordis.patch.yml` 的用户。
3
+ **MCP 服务器 + 用户 Skills + 魔搭市场浏览三合一设置页**(`v0.3.6`)。适合需要在 DSH 里频繁增删改 MCP server / skills、又不想手改 `cordis.patch.yml` 的用户。
4
4
 
5
5
  ## Requirements
6
6
 
7
7
  - DeepSeek Harness 带 `web` profile GUI(`npx @deepseek-ai/dsh web`)
8
8
  - Node.js `^22.19` 或 `>=24`
9
9
  - `@deepseek-ai/schemastery` `3.18.1` / `3.18.2` / `3.18.4`(peer 枚举,供工具过滤的 Config 声明用;无则过滤静默全放行)
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)
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)、**0.1.7-rc.2**(0.3.5:rc.1→rc.2 tarball 逐文件 diff 零 API 变化 + rc.2 真机回归实测通过;0.3.6 再修 CRLF 解析 + 取当前 profile 补丁路径,2026-09-25)
11
+ - **`@deepseek-ai/dsh` peer 声明 `0.1.7-rc.1 || 0.1.7-rc.2`**(0.3.4 起逐版本枚举,0.3.5 追加 rc.2;规范见 `AGENTS.md` 规范 3)
12
12
 
13
13
  ## 兼容性门禁(0.3.4 起声明)
14
14
 
@@ -16,7 +16,7 @@
16
16
 
17
17
  ```json
18
18
  "peerDependencies": {
19
- "@deepseek-ai/dsh": "0.1.7-rc.1"
19
+ "@deepseek-ai/dsh": "0.1.7-rc.1 || 0.1.7-rc.2"
20
20
  }
21
21
  ```
22
22
 
@@ -24,11 +24,12 @@
24
24
 
25
25
  | 运行中的 DSH | 行为 |
26
26
  |---|---|
27
- | **`0.1.7-rc.1`**(本次实测) | ✅ `evaluatePluginCompatibility()` 返回 `undefined` ⇒ 正常加载,与 0.3.3 行为一致 |
27
+ | **`0.1.7-rc.1`**(实测,2026-09-24) | ✅ `evaluatePluginCompatibility()` 返回 `undefined` ⇒ 正常加载,与 0.3.3 行为一致 |
28
+ | **`0.1.7-rc.2`**(实测,2026-09-25) | ✅ 同上:rc.1→rc.2 tarball 逐文件 diff 显示 shell/settings/credentials 的 `lib/` **零变化**(仅 README/package.json 版本号)、app-boot 的变更全部与插件无关;另在 scratch profile(`dsh@0.1.7-rc.2` + 本插件 0.3.4)真机回归 `/connector/api/mcp/filters` → 200 |
28
29
  | **`0.1.7` 及更新的、未实测版本** | ⛔ 门禁拦下:启动时**整个 bundle 被跳过**(stderr 打 `skipping profile bundle "@omdp/dsh-connector"`),安装时该行被置灰 |
29
30
  | **`≤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
 
31
- **为什么故意只写 `0.1.7-rc.1`(而不是写老版本)**:老版本运行时压根不执行这个检查,写进去纯属装饰、无法被验证;而**未实测的新版本必须被拦住**——这正是 `AGENTS.md` 规范 3 的要求(`peerDependencies` 只精确枚举实测过的版本,禁止开放范围,未核查的版本宁可报 unmet peer 也不得预先放行)。门禁被触发时是**优雅跳过**(插件不加载、DSH 照常启动),符合本插件「硬保证 = DSH 不会因插件崩溃」的抗崩溃设计。
32
+ **为什么只写精确枚举(而不是开放范围,也不写未实测的老版本)**:`≤0.1.6` 的运行时压根不执行这个检查,写进去纯属装饰、无法被验证;而**未实测的新版本必须被拦住**——这正是 `AGENTS.md` 规范 3 的要求(`peerDependencies` 只精确枚举实测过的版本,禁止开放范围,未核查的版本宁可报 unmet peer 也不得预先放行)。门禁被触发时是**优雅跳过**(插件不加载、DSH 照常启动),符合本插件「硬保证 = DSH 不会因插件崩溃」的抗崩溃设计。
32
33
 
33
34
  > 升级 DSH 后若设置页突然看不到 Connector 标签,先看启动日志有没有 `skipping profile bundle`——那就是门禁在提醒你「该插件尚未针对这个 DSH 版本做兼容核查」,核查通过后把该版本追加进上面的枚举并同步更新 `docs/plugin-compatibility.md`。
34
35
 
@@ -321,6 +322,32 @@ skills/mcp 列表与详情、证书/Hosted 标识、安装命令、记录来源
321
322
 
322
323
  ## 变更记录
323
324
 
325
+ - **0.3.6**(2026-09-25):**修两个真机 bug —— CRLF 补丁文件解析 + 编辑错 profile 的补丁文件**。
326
+ 1. **CRLF 兼容**:`parseMcpServers()` 的键值正则 `^\s+(\w+):\s*(.*)$` 在 CRLF 文件上必然失配
327
+ (JS 里 `.*` 不匹配 `\r`、`$` 也不匹配 `\r` 之前的位置),于是每行的
328
+ `transport`/`serverName`/`command`/`url` 全部落进 `preserve` 桶:API 返回
329
+ `transport:"" serverName:""`,UI 的「工具过滤」区因 `if (!props.serverName) return null`
330
+ **静默消失**、transport 徽标退化成 `stdio`。修复:解析前用 `toLf()` 归一化换行(连同
331
+ `stripQuotes`/`stripListScalar` 的 `$` 正则一起恢复),写回时也统一成 LF。
332
+ 验证:同一份配置文本按 LF / CRLF 两种形态跑解析,修复前 CRLF 五项断言全红、修复后 14/14 通过。
333
+ 2. **profile 补丁路径**:`patchPath()` 曾硬编码 `profiles/web/cordis.patch.yml`,在桌面端
334
+ (跑 `profiles/desktop`)会去改 **另一个 profile** 的文件——写下去对自己的 MCP 配置毫无影响,
335
+ UI 提示还把错误路径显示给用户。修复:0.1.7+ 从 `ctx.get('profileContext').patchPath` 取当前
336
+ profile 的真实路径(`dsh-app-boot` 的 `ProfileContext` 契约),无该服务(0.1.5/0.1.6)时保留
337
+ 历史路径;`GET /api/mcp` 一并回传 `patchPath`,设置页提示改成显示真实路径。
338
+ 触发场景:用户手改(或编辑器保存)`cordis.patch.yml` 使其变成 CRLF,桌面端点开
339
+ 「Connector → MCP 服务器」就只剩 4 张光秃秃的卡片。详见
340
+ `notes/2026-09-25/debug/connector-crlf-patch-parser-and-profile-path.md`。
341
+ - **0.3.5**(2026-09-25):**追加 DSH `0.1.7-rc.2` 支持**(peer 枚举 `0.1.7-rc.1 || 0.1.7-rc.2`,代码零改动)。
342
+ 背景:DSH 桌面版(DeepSeek Harness desktop)0.1.7-rc.2 上线后,门禁把只声明 `0.1.7-rc.1` 的 0.3.4 拦下
343
+ (`skipping profile bundle`,插件列表显示「异常」)。核查:rc.1→rc.2 npm tarball 逐文件 diff——
344
+ `dsh-shell`/`dsh-settings`/`dsh-credentials` 的 `lib/` **逐字节零变化**(仅 README/package.json),
345
+ `dsh-llm` 仅新增内容类型与错误码(不改既有签名),`dsh-app-boot` 的变更全部与插件无关
346
+ (`skippedBundles` 诊断重构、`generateConfigSchema` 内部签名);再用 rc.2 的
347
+ `evaluatePluginCompatibility()` 执行新声明 → `undefined`(放行)。回归实测:scratch profile
348
+ (`dsh@0.1.7-rc.2` + 插件 0.3.4 + 精确版本豁免)真机启动 → `/connector/api/mcp/filters` 200。
349
+ **教训**:`0.1.7-rc.1` 这类 rc 枚举在 rc.2 发布当天就会过时——rc 系列每个新 rc 都要重新核查并追加。
350
+
324
351
  - **0.3.4**(2026-09-24):**声明 DSH 版本支持 + 修复工具过滤被静默清空**。
325
352
  1. **`@deepseek-ai/dsh` peer 声明 `0.1.7-rc.1`**(逐版本枚举,语义见上方「## 兼容性门禁」)。
326
353
  用 DSH 真实的 `evaluatePluginCompatibility()` 逐一验证:`0.1.7-rc.1` → 正常加载;
package/client.js CHANGED
@@ -412,7 +412,10 @@ window.__ModuleLoader__.load({
412
412
  ),
413
413
  err ? createElement('div', { className: 'pm_err' }, err) : null,
414
414
  createElement('div', { className: 'pm_meta' },
415
- props.mcp.exists ? '编辑 profiles/web/cordis.patch.yml 中的 mcp-* 块。保存后重启 dsh 生效。Edit the mcp-* block in cordis.patch.yml; restart dsh to apply.' : '未找到 cordis.patch.yml。cordis.patch.yml not found.'),
415
+ props.mcp.exists
416
+ // host 回传真实路径(0.1.7+ 的 profileContext.patchPath);老 host 没这个字段时退回笼统说法。
417
+ ? '编辑 ' + (props.mcp.patchPath || '本 profile 的 cordis.patch.yml') + ' 中的 mcp-* 块。保存后重启 dsh 生效。Edit the mcp-* block in cordis.patch.yml; restart dsh to apply.'
418
+ : '未找到 cordis.patch.yml。cordis.patch.yml not found.'),
416
419
  rows,
417
420
  editing === null
418
421
  ? createElement('button', { className: 'pm_btn pm_btn ghost', onClick: function () { setEditing({ id: 'mcp-new', customId: '', name: '', transport: 'stdio', serverName: '', url: '', command: '', args: '' }) } }, '+ 添加 MCP 服务器 Add MCP Server')
package/index.js CHANGED
@@ -95,6 +95,9 @@ let _legacyScope = null // rc.x: settings.register(...) 返回的 scope
95
95
  let _legacySvc = null // rc.x: settings 服务(读用 get,写用 update)
96
96
  let _settingsSvc = null // 0.1.7+: ctx.settings(迁移写回用,见 ensureLegacyFiltersMigration)
97
97
  let _migrationAttempted = false
98
+ // 0.1.7+ 的 profileContext.patchPath:本进程真正在用的 profile 补丁文件。
99
+ // 0.1.5/0.1.6 没有这个服务,回退到历史路径(见 patchPath)。
100
+ let _profilePatchPath = null
98
101
  function readToolFilters() {
99
102
  try {
100
103
  // 优先 0.1.7+ 的活 Ref(.get() 每次返回最新值)
@@ -191,8 +194,13 @@ function resolveHome() {
191
194
  : join(homedir(), '.dsh')
192
195
  }
193
196
 
197
+ // 本进程要编辑的 profile 补丁文件。
198
+ // 0.1.7 起 dsh 通过 profileContext 服务提供当前 profile 的绝对路径(desktop / web /
199
+ // 自定义 profile 都不同),必须用它;否则桌面端会去改 profiles/web 的文件 —— 写下去
200
+ // 对桌面端自己的 MCP 配置毫无影响,还会误导 UI 提示(0.3.5 及以前的 bug)。
201
+ // 0.1.5/0.1.6 没有 profileContext:退回历史硬编码路径保持老行为。
194
202
  function patchPath() {
195
- return join(resolveHome(), 'profiles', 'web', 'cordis.patch.yml')
203
+ return _profilePatchPath ?? join(resolveHome(), 'profiles', 'web', 'cordis.patch.yml')
196
204
  }
197
205
 
198
206
  function skillsRoot() {
@@ -235,6 +243,13 @@ function stripQuotes(v) {
235
243
  return v
236
244
  }
237
245
 
246
+ // Normalize any line ending (CRLF / lone CR) to LF. Every line-based parser here
247
+ // anchors regexes with `$`, which in JS does not match before a `\r`, so a CRLF
248
+ // profile patch silently loses fields instead of failing loudly.
249
+ function toLf(text) {
250
+ return String(text).replace(/\r\n?/g, '\n')
251
+ }
252
+
238
253
  // Extract the bare scalar from a YAML block-sequence item line like
239
254
  // ` - '--transport'` -> `--transport`. Handles quoted and unquoted values.
240
255
  function stripListScalar(line) {
@@ -265,7 +280,12 @@ const SKIP_KEYS = new Set(['name', 'config'])
265
280
  * (env blocks, `!!js` lines, any other keys) so a rewrite never drops them.
266
281
  */
267
282
  function parseMcpServers(blockText) {
268
- const lines = blockText.split('\n')
283
+ // 先统一换行:CRLF 文件里每行都以 `\r` 结尾,而下面的键值正则
284
+ // `^\s+(\w+):\s*(.*)$` 中 `.*` 不匹配 `\r`、`$` 也不匹配 `\r` 之前的位置,
285
+ // 于是一整行的 transport/serverName/command/url 全部匹配失败、统统落进
286
+ // `preserve` —— serverName 空 → UI 工具过滤区静默消失(`if (!props.serverName) return null`)。
287
+ // 归一化后与 LF 文件完全同构,顺带让 stripQuotes/stripListScalar 的 `$` 正则重新生效。
288
+ const lines = toLf(blockText).split('\n')
269
289
  const servers = []
270
290
  let cur = null
271
291
  let curIndent = 0
@@ -1024,6 +1044,18 @@ async function readBody(req) {
1024
1044
  }
1025
1045
 
1026
1046
  export function apply(ctx, config) {
1047
+ // ── 绑定本进程的 profile 补丁文件(0.1.7+)──
1048
+ // profileContext 由 dsh 启动器提供,patchPath 就是当前 profile 的
1049
+ // cordis.patch.yml 绝对路径;没有它就只能沿用历史硬编码路径。
1050
+ try {
1051
+ const profile = ctx.get('profileContext')
1052
+ if (profile && typeof profile.patchPath === 'string' && profile.patchPath.length > 0) {
1053
+ _profilePatchPath = profile.patchPath
1054
+ console.info('[dsh-connector] profile patch file:', profile.name ? `${profile.name} -> ${profile.patchPath}` : profile.patchPath)
1055
+ ctx.effect(() => () => { _profilePatchPath = null }, 'connector: release profile patch path')
1056
+ }
1057
+ } catch {}
1058
+
1027
1059
  // ── 工具过滤三件套之 (0):判定配置后端 + 快照缓存 ──
1028
1060
  // 0.1.7+:config.toolFilters 是 volatile Ref(.get() 每次返回最新值,编辑热生效)。
1029
1061
  // rc.x:无 volatile,退回老的 settings.register/get/update(0.3.0 的原始实现)。
@@ -1106,7 +1138,7 @@ export function apply(ctx, config) {
1106
1138
  }
1107
1139
  const found = findMcpBlock(text)
1108
1140
  const servers = found ? parseMcpServers(found.blockText) : []
1109
- return json(res, 200, { servers, exists: true, version: '0.1.1' })
1141
+ return json(res, 200, { servers, exists: true, version: '0.1.2', patchPath: patchPath() })
1110
1142
  }
1111
1143
 
1112
1144
  // POST /api/mcp — replace server entries. Preserve env/!!js from the
@@ -1141,7 +1173,7 @@ export function apply(ctx, config) {
1141
1173
  const prev = existing.get(s.id)
1142
1174
  return prev ? { ...s, preserve: s.preserve || prev.preserve } : s
1143
1175
  })
1144
- const next = buildPatch(text, servers)
1176
+ const next = toLf(buildPatch(text, servers))
1145
1177
  // Validate the entire resulting patch parses as YAML before writing.
1146
1178
  // cordis.patch.yml uses `!!js` tags; the parser tolerates them via
1147
1179
  // silent log level (values stay raw, nothing printed), so a successful
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omdp/dsh-connector",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
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,7 +46,7 @@
46
46
  "jsdom": "^24.1.3"
47
47
  },
48
48
  "peerDependencies": {
49
- "@deepseek-ai/dsh": "0.1.7-rc.1",
49
+ "@deepseek-ai/dsh": "0.1.7-rc.1 || 0.1.7-rc.2",
50
50
  "@deepseek-ai/schemastery": "3.18.1 || 3.18.2 || 3.18.4"
51
51
  }
52
52
  }