@max-null/dsh-skill-mcp-center 0.5.1 → 0.5.3

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
@@ -10,7 +10,7 @@ Skill 与 MCP 管理中心:在设置里管理 skills 与 MCP 服务器,右
10
10
 
11
11
  ## Features / 功能
12
12
 
13
- - **Skill management / Skill 管理** — browse every skill by tier (system / user / workspace / runtime), toggle model invocation via the `disable-model-invocation` frontmatter (disk-backed skills only). Covers skills shipped **inside plugin packages** (e.g. `@max-null/dsh-skills`), not just the user-level roots — the host-level skill filesystem is disabled in web-app, so a plugin's own `skills/` directory would otherwise be invisible while its skills are loaded and in effect.
13
+ - **Skill management / Skill 管理** — browse every skill grouped by source (Global / Workspace / Built-in DSH official, plus one group per plugin that ships skills), toggle model invocation via the `disable-model-invocation` frontmatter (disk-backed skills only). Covers skills shipped **inside plugin packages** (e.g. `@max-null/dsh-skills`), not just the user-level roots — the host-level skill filesystem is disabled in web-app, so a plugin's own `skills/` directory would otherwise be invisible while its skills are loaded and in effect.
14
14
  - **MCP management / MCP 管理** — add / edit / remove `mcp-client` servers, enable/disable without deleting config, all **hot-applied** through `ctx.loader` (no restart).
15
15
  - **Live status / 实时状态** — a sidebar "MCP" tab (via `dsh-better-sidebar`) showing per-server connection state; **click a server row to expand its tools' names and descriptions** (collapsed by default — a server can expose dozens of them). Names come from `tools.schemas()`; the redundant `mcp__<server>__` prefix is hidden while the full name stays on hover. Polled while visible and following the session.
16
16
  - **Skin-compatible / 皮肤兼容** — every color uses `var(--dsw-*)` tokens.
package/dist/service.d.ts CHANGED
@@ -74,6 +74,15 @@ export interface McpConfig {
74
74
  url?: string;
75
75
  headers?: Record<string, string>;
76
76
  }
77
+ /**
78
+ * Scan one root for SKILL.md entries and parse their frontmatter.
79
+ * @param root - directory to scan; a missing root yields no skills.
80
+ * @param source - discovery source label recorded on every skill found.
81
+ * @param writable - whether the surface may rewrite these SKILL.md files.
82
+ * @param provider - provider label recorded on every skill found.
83
+ * @returns the skills this root contributes, in directory order.
84
+ */
85
+ export declare function scanSkillRoot(root: string, source: string, writable?: boolean, provider?: string): Promise<SkillView[]>;
77
86
  export declare class SkillMcpService extends Service {
78
87
  static inject: string[];
79
88
  private readonly officialSkillDirs;
package/dist/service.js CHANGED
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { Service } from '@deepseek-ai/cordis';
10
10
  import { existsSync } from 'node:fs';
11
- import { readdir, readFile, writeFile } from 'node:fs/promises';
11
+ import { readdir, readFile, stat, writeFile } from 'node:fs/promises';
12
12
  import { homedir } from 'node:os';
13
13
  import { join, sep } from 'node:path';
14
14
  import { fileURLToPath } from 'node:url';
@@ -65,16 +65,45 @@ function ownNodeModules() {
65
65
  const at = here.lastIndexOf(marker);
66
66
  return at < 0 ? null : here.slice(0, at + marker.length - 1);
67
67
  }
68
- /** Absolute SKILL.md path for one directory/file entry, or null. */
69
- function skillPathFor(root, name, isDirectory) {
68
+ /**
69
+ * Absolute SKILL.md path for one root entry, or null when the entry is neither
70
+ * a directory nor a Markdown file.
71
+ *
72
+ * `readdir`'s Dirent reports lstat semantics: a Windows directory junction —
73
+ * and equally a POSIX symlink to a directory — is `isDirectory() === false`
74
+ * with `isSymbolicLink() === true`, so the Dirent alone cannot tell a linked
75
+ * directory from a plain file. Only entries the Dirent leaves open reach the
76
+ * `stat` probe, which follows the link to its target type and so keeps the
77
+ * common true-directory case at zero extra syscalls. A broken link fails that
78
+ * probe (ENOENT) and yields null rather than a path nothing can read.
79
+ * @param root - directory being scanned.
80
+ * @param entry - one `readdir(root, { withFileTypes: true })` entry.
81
+ * @returns the SKILL.md path this entry names, or null when it names no skill.
82
+ */
83
+ async function skillPathFor(root, entry) {
84
+ const full = join(root, entry.name);
85
+ let isDirectory = entry.isDirectory();
86
+ if (!isDirectory && !entry.isFile()) {
87
+ try {
88
+ isDirectory = (await stat(full)).isDirectory();
89
+ }
90
+ catch {
91
+ return null; // broken link: the target is gone
92
+ }
93
+ }
70
94
  if (isDirectory)
71
- return join(root, name, 'SKILL.md');
72
- if (name.endsWith('.md'))
73
- return join(root, name);
74
- return null;
95
+ return join(full, 'SKILL.md');
96
+ return entry.name.endsWith('.md') ? full : null;
75
97
  }
76
- /** Scan one root for SKILL.md entries and parse their frontmatter. */
77
- async function scanSkillRoot(root, source, writable = true, provider = 'filesystem') {
98
+ /**
99
+ * Scan one root for SKILL.md entries and parse their frontmatter.
100
+ * @param root - directory to scan; a missing root yields no skills.
101
+ * @param source - discovery source label recorded on every skill found.
102
+ * @param writable - whether the surface may rewrite these SKILL.md files.
103
+ * @param provider - provider label recorded on every skill found.
104
+ * @returns the skills this root contributes, in directory order.
105
+ */
106
+ export async function scanSkillRoot(root, source, writable = true, provider = 'filesystem') {
78
107
  let entries;
79
108
  try {
80
109
  entries = await readdir(root, { withFileTypes: true });
@@ -84,7 +113,7 @@ async function scanSkillRoot(root, source, writable = true, provider = 'filesyst
84
113
  }
85
114
  const skills = [];
86
115
  for (const entry of entries) {
87
- const skillPath = skillPathFor(root, entry.name, entry.isDirectory());
116
+ const skillPath = await skillPathFor(root, entry);
88
117
  if (skillPath === null)
89
118
  continue;
90
119
  let text;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@max-null/dsh-skill-mcp-center",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "Skill & MCP management center for DeepSeek Harness — manage skills and MCP servers in Settings, live MCP status in the sidebar",
5
5
  "license": "MIT",
6
6
  "publishConfig": {
@@ -67,18 +67,18 @@
67
67
  "peerDependencies": {
68
68
  "@deepseek-ai/cordis": "^4.0.1",
69
69
  "@deepseek-ai/cordis-plugin-loader": "^1.0.2",
70
- "@deepseek-ai/dsh-client-connection": "^0.1.1-rc.1",
71
- "@deepseek-ai/dsh-host-apiproxy": "^0.1.1-rc.1",
72
- "@deepseek-ai/dsh-skill": "^0.1.1-rc.1",
73
- "@deepseek-ai/dsh-tools": "^0.1.1-rc.1"
70
+ "@deepseek-ai/dsh-client-connection": ">=0.1.1-rc.1 <0.3.0",
71
+ "@deepseek-ai/dsh-host-apiproxy": ">=0.1.1-rc.1 <0.3.0",
72
+ "@deepseek-ai/dsh-skill": ">=0.1.1-rc.1 <0.3.0",
73
+ "@deepseek-ai/dsh-tools": ">=0.1.1-rc.1 <0.3.0"
74
74
  },
75
75
  "devDependencies": {
76
76
  "@deepseek-ai/cordis": "^4.0.1",
77
77
  "@deepseek-ai/cordis-plugin-loader": "^1.0.2",
78
- "@deepseek-ai/dsh-client-connection": "^0.1.1-rc.1",
79
- "@deepseek-ai/dsh-host-apiproxy": "^0.1.1-rc.1",
80
- "@deepseek-ai/dsh-skill": "^0.1.1-rc.1",
81
- "@deepseek-ai/dsh-tools": "^0.1.1-rc.1",
78
+ "@deepseek-ai/dsh-client-connection": ">=0.1.1-rc.1 <0.3.0",
79
+ "@deepseek-ai/dsh-host-apiproxy": ">=0.1.1-rc.1 <0.3.0",
80
+ "@deepseek-ai/dsh-skill": ">=0.1.1-rc.1 <0.3.0",
81
+ "@deepseek-ai/dsh-tools": ">=0.1.1-rc.1 <0.3.0",
82
82
  "@types/js-yaml": "^4.0.9",
83
83
  "@types/node": "^26.2.0",
84
84
  "@types/react": "~18.3.1",
package/release-notes.md CHANGED
@@ -1,5 +1,47 @@
1
1
  # Release Notes — @max-null/dsh-skill-mcp-center
2
2
 
3
+ ## 0.5.3 (2026-10-02)
4
+
5
+ ### 修复
6
+
7
+ - **Windows 下技能列表整体为空**。`~/.dsh/skills` 下的条目是目录 junction 或符号
8
+ 链接时,`scanSkillRoot()` 拿 `Dirent.isDirectory()` 当形态判据——而 Dirent 是
9
+ lstat 语义,junction 报 `isDirectory() === false` / `isSymbolicLink() === true`,
10
+ 于是 `skillPathFor()` 返回 null,每个链接型技能在 `continue` 处被丢弃。技能本身
11
+ 完好可读,只是扫不到,面板上表现为列表为空。
12
+
13
+ 判据改为**分层**:Dirent 能明确作答时直接采信(真目录走 `isDirectory()`、真文件
14
+ 走 `isFile()`),只有链接与文件系统报 `DT_UNKNOWN` 的形态才跟随一次 `stat`。真
15
+ 目录因此仍是零额外系统调用。断链在 `stat` 处失败即跳过,同根其它技能不受影响。
16
+ 这不是 Windows 专用分支——junction 与 POSIX 符号链接走同一条路。
17
+
18
+ ### 兼容性
19
+
20
+ - 无接口变更:`listSkills()` 的出入参、端点集合与请求信封均未改,只是扫描判据放宽。
21
+ 新增 5 条单测覆盖真目录 / `.md` 文件 / 链接指向目录 / 断链 / 非技能条目。
22
+
23
+ ## 0.5.2 (2026-09-29)
24
+
25
+ ### 变更
26
+
27
+ - 放宽 `@deepseek-ai/dsh-*` 的版本范围:`peerDependencies` 与 `devDependencies` 中的
28
+ `dsh-client-connection`、`dsh-host-apiproxy`、`dsh-skill`、`dsh-tools` 由 `^0.1.1-rc.1`
29
+ 改为 `>=0.1.1-rc.1 <0.3.0` —— 保留下限、只放宽上界,旧内核(0.1.7-rc.2)仍然满足。
30
+
31
+ ### 为什么
32
+
33
+ - 内核侧逐个检查插件 `peerDependencies` 里的每个 `@deepseek-ai/*` 声明,判据是标准 semver:
34
+ `semver.satisfies(runtimeVersion, requirement, { includePrerelease: true })`。
35
+ - `^0.1.1-rc.1` 展开为 `>=0.1.1-rc.1 <0.2.0-0`,对 `0.2.0-rc.1` 判 **false**;判定失败的插件
36
+ 会被**静默跳过**——不进 fiber graph,也不出现在「did not activate」列表里,用户侧的表现
37
+ 是「插件装了却毫无反应」,没有任何日志可查。
38
+ - `>=0.1.1-rc.1 <0.3.0` 在 semver 7.7.4 下对 `0.1.7-rc.2` 与 `0.2.0-rc.1` 均判 true(已实测)。
39
+
40
+ ### 兼容性
41
+
42
+ - 无运行时行为变更:端点集合、请求体、响应信封与代码路径均未改,只是依赖声明放宽。
43
+ - 面板端与宿主端仍需同版本(传输策略见 0.5.1)。
44
+
3
45
  ## 0.5.1 (2026-09-17)
4
46
 
5
47
  ### 修复