@max-null/dsh-skill-mcp-center 0.5.3 → 0.5.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.
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 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.
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. Also covers a plugin that mounts `@deepseek-ai/dsh-skill-filesystem` under an id of its own and points `bundledSkillDir` at its skills (e.g. `dsh-plugin-zhihu-search`), including when the patch computes that path with a `!!js` expression — such a path is read from the entry's fiber, where the Loader has already evaluated it.
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
@@ -83,13 +83,55 @@ export interface McpConfig {
83
83
  * @returns the skills this root contributes, in directory order.
84
84
  */
85
85
  export declare function scanSkillRoot(root: string, source: string, writable?: boolean, provider?: string): Promise<SkillView[]>;
86
+ /** The loader-entry fields plugin skill discovery reads. */
87
+ export interface SkillRootEntry {
88
+ readonly options: {
89
+ readonly id: string;
90
+ readonly name: string;
91
+ };
92
+ /** Absent while the entry is disabled, incompatible, or still loading. */
93
+ readonly fiber?: {
94
+ readonly config: unknown;
95
+ } | undefined;
96
+ }
97
+ /**
98
+ * Skill roots contributed by loaded plugin packages.
99
+ *
100
+ * Two declarations reach such a directory, and both are needed:
101
+ *
102
+ * - the entry's module specifier, i.e. `<node_modules>/<pkg>/skills` — how
103
+ * `@max-null/dsh-skills` and `@max-null/dsh-plugin-center` ship theirs;
104
+ * - the entry's own `bundledSkillDir` config — the only way to see a plugin
105
+ * that mounts the official `@deepseek-ai/dsh-skill-filesystem` under an id
106
+ * of its own. `dsh-plugin-zhihu-search` is the live sample: it registers
107
+ * `name: '@deepseek-ai/dsh-skill-filesystem'`, and its `skills/` sits in a
108
+ * different `node_modules` tree than this plugin's.
109
+ *
110
+ * `bundledSkillDir` is read from the **fiber**, never from
111
+ * `entry.options.config`. A patch may compute the value with a `!!js`
112
+ * expression, and the Loader keeps that expression as a `{ __jsExpr }` node
113
+ * in the stored options while handing the owning fiber the evaluated copy —
114
+ * it interpolates on the `internal/config` waterfall and writes the raw node
115
+ * back on `internal/update` specifically so file write-back preserves the
116
+ * `!!js` form. The stored value is therefore an expression node rather than a
117
+ * path, and reading it can only miss.
118
+ *
119
+ * Only **loaded** entries are probed, so this costs a couple of existence
120
+ * checks per plugin instead of a scan of the whole `node_modules` tree.
121
+ * @param entries - loaded loader entries.
122
+ * @param nodeModules - this package's own `node_modules` tree, or null.
123
+ * @returns the skill roots that exist, deduplicated, in entry order.
124
+ */
125
+ export declare function pluginSkillRoots(entries: Iterable<SkillRootEntry>, nodeModules: string | null): {
126
+ dir: string;
127
+ label: string;
128
+ }[];
86
129
  export declare class SkillMcpService extends Service {
87
130
  static inject: string[];
88
131
  private readonly officialSkillDirs;
89
132
  constructor(ctx: Context, config?: SkillConfig);
90
133
  /**
91
- * Skill roots that live **inside loaded plugin packages**, i.e.
92
- * `<node_modules>/<pkg>/skills`.
134
+ * Skill roots that live **inside loaded plugin packages**.
93
135
  *
94
136
  * The host-level skill filesystem is disabled in web-app (presets own
95
137
  * discovery), so a plugin that ships skills — `@max-null/dsh-skills` and
@@ -97,9 +139,6 @@ export declare class SkillMcpService extends Service {
97
139
  * package, where no user-level root can see them. Without this the
98
140
  * management surface showed 17 user skills while 8 plugin skills were loaded
99
141
  * and in effect (2026-09-14 用户报「skill 生效但不展示」).
100
- *
101
- * Only **loaded** entries are probed, so this costs one existence check per
102
- * plugin rather than a scan of the whole node_modules tree.
103
142
  */
104
143
  private pluginSkillDirs;
105
144
  /**
package/dist/service.js CHANGED
@@ -139,6 +139,57 @@ export async function scanSkillRoot(root, source, writable = true, provider = 'f
139
139
  }
140
140
  return skills;
141
141
  }
142
+ /**
143
+ * Skill roots contributed by loaded plugin packages.
144
+ *
145
+ * Two declarations reach such a directory, and both are needed:
146
+ *
147
+ * - the entry's module specifier, i.e. `<node_modules>/<pkg>/skills` — how
148
+ * `@max-null/dsh-skills` and `@max-null/dsh-plugin-center` ship theirs;
149
+ * - the entry's own `bundledSkillDir` config — the only way to see a plugin
150
+ * that mounts the official `@deepseek-ai/dsh-skill-filesystem` under an id
151
+ * of its own. `dsh-plugin-zhihu-search` is the live sample: it registers
152
+ * `name: '@deepseek-ai/dsh-skill-filesystem'`, and its `skills/` sits in a
153
+ * different `node_modules` tree than this plugin's.
154
+ *
155
+ * `bundledSkillDir` is read from the **fiber**, never from
156
+ * `entry.options.config`. A patch may compute the value with a `!!js`
157
+ * expression, and the Loader keeps that expression as a `{ __jsExpr }` node
158
+ * in the stored options while handing the owning fiber the evaluated copy —
159
+ * it interpolates on the `internal/config` waterfall and writes the raw node
160
+ * back on `internal/update` specifically so file write-back preserves the
161
+ * `!!js` form. The stored value is therefore an expression node rather than a
162
+ * path, and reading it can only miss.
163
+ *
164
+ * Only **loaded** entries are probed, so this costs a couple of existence
165
+ * checks per plugin instead of a scan of the whole `node_modules` tree.
166
+ * @param entries - loaded loader entries.
167
+ * @param nodeModules - this package's own `node_modules` tree, or null.
168
+ * @returns the skill roots that exist, deduplicated, in entry order.
169
+ */
170
+ export function pluginSkillRoots(entries, nodeModules) {
171
+ const out = [];
172
+ const seen = new Set();
173
+ const add = (dir, label) => {
174
+ if (seen.has(dir) || !existsSync(dir))
175
+ return;
176
+ seen.add(dir);
177
+ out.push({ dir, label });
178
+ };
179
+ for (const entry of entries) {
180
+ const name = entry.options.name;
181
+ if (nodeModules !== null && name !== '' && !name.startsWith('.') && !name.startsWith('/')) {
182
+ add(join(nodeModules, ...name.split('/'), 'skills'), `plugin:${name}`);
183
+ }
184
+ const config = entry.fiber?.config;
185
+ const declared = typeof config === 'object' && config !== null && 'bundledSkillDir' in config
186
+ ? config.bundledSkillDir
187
+ : undefined;
188
+ if (typeof declared === 'string')
189
+ add(declared, `plugin:${entry.options.id}`);
190
+ }
191
+ return out;
192
+ }
142
193
  export class SkillMcpService extends Service {
143
194
  static inject = ['loader', 'tools'];
144
195
  officialSkillDirs;
@@ -147,8 +198,7 @@ export class SkillMcpService extends Service {
147
198
  this.officialSkillDirs = config.officialSkillDirs ?? [];
148
199
  }
149
200
  /**
150
- * Skill roots that live **inside loaded plugin packages**, i.e.
151
- * `<node_modules>/<pkg>/skills`.
201
+ * Skill roots that live **inside loaded plugin packages**.
152
202
  *
153
203
  * The host-level skill filesystem is disabled in web-app (presets own
154
204
  * discovery), so a plugin that ships skills — `@max-null/dsh-skills` and
@@ -156,24 +206,9 @@ export class SkillMcpService extends Service {
156
206
  * package, where no user-level root can see them. Without this the
157
207
  * management surface showed 17 user skills while 8 plugin skills were loaded
158
208
  * and in effect (2026-09-14 用户报「skill 生效但不展示」).
159
- *
160
- * Only **loaded** entries are probed, so this costs one existence check per
161
- * plugin rather than a scan of the whole node_modules tree.
162
209
  */
163
210
  pluginSkillDirs() {
164
- const nm = ownNodeModules();
165
- if (nm === null)
166
- return [];
167
- const out = [];
168
- for (const entry of this.ctx.loader.entries()) {
169
- const name = entry.options.name;
170
- if (typeof name !== 'string' || name === '' || name.startsWith('.') || name.startsWith('/'))
171
- continue;
172
- const dir = join(nm, ...name.split('/'), 'skills');
173
- if (existsSync(dir))
174
- out.push({ dir, label: `plugin:${name}` });
175
- }
176
- return out;
211
+ return pluginSkillRoots(this.ctx.loader.entries(), ownNodeModules());
177
212
  }
178
213
  /**
179
214
  * User-level skills, project-level skills for the given workspace, skills
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@max-null/dsh-skill-mcp-center",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
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": {
package/release-notes.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Release Notes — @max-null/dsh-skill-mcp-center
2
2
 
3
+ ## 0.5.4 (2026-10-04)
4
+
5
+ ### 修复
6
+
7
+ - **插件用 `bundledSkillDir` 注册的技能扫不到**。面板原先只认一种形态:拿 loader
8
+ entry 的 `name` 当包名,去拼 `<node_modules>/<包名>/skills`。插件若把官方
9
+ `@deepseek-ai/dsh-skill-filesystem` 挂在自己的 id 下、用 `bundledSkillDir` 指向
10
+ 自带 `skills/`(活样本 `dsh-plugin-zhihu-search`),说明符就是**另一个包名**,
11
+ 拼出的路径不存在;而它的技能往往还在**另一棵** `node_modules` 树里。
12
+
13
+ 真正的判据比「拼错了包名」更深一层:`bundledSkillDir` 在 `entry.options.config`
14
+ 里保留的是 `!!js` 表达式节点(`{ __jsExpr }`),**不是路径**——Loader 只把求值后的
15
+ 副本交给 entry 的 fiber,存下来的那份要留着让文件写回保住 `!!js` 写法。所以照
16
+ options 去拼路径必然落空,且**不报错**(静默漏掉)。
17
+
18
+ 修法:该配置**从 fiber 读**,两条声明都探,并对同一目录去重。
19
+
20
+ ### 兼容性
21
+
22
+ - 无接口变更:`listSkills()` 的出入参、端点集合与请求信封均未改,只是发现来源多了
23
+ 一条。插件技能仍是只读展示(`writable: false`),未开放开关。
24
+ - 新增 12 条测试:10 条覆盖根发现(含「options 里的表达式节点不被当路径采信」这一
25
+ 关键回归),2 条用真 cordis + 真 Loader 坐实「options 上是节点、fiber 上是求值后
26
+ 的路径」。后者会在上游改变该行为时变红。
27
+
3
28
  ## 0.5.3 (2026-10-02)
4
29
 
5
30
  ### 修复