dsh-plugin-manager-plus 1.3.0 → 1.3.2

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
@@ -79,6 +79,8 @@ dsh plugin --profile web add dsh-plugin-manager-plus
79
79
  > name: dsh-plugin-manager-plus
80
80
  > ```
81
81
 
82
+ **bundle 形态插件(自带 `dsh.bundle.patch` 的包)**:v1.3.1 起自动识别——安装时**不再**写入家级插入行(bundle 层已由官方 CLI 自动挂载,再写一条会触发同包双 Loader 源致命错误),只记录到 manifest 供 UI 管理。此类插件在「插件管理」中显示为社区来源、可启停/卸载,其激活行随包安装/卸载自动存在/消失。
83
+
82
84
  ### 方式二:git clone + 接线脚本(本地开发)
83
85
 
84
86
  ```powershell
package/lib/client.js CHANGED
@@ -74,7 +74,24 @@ window.__ModuleLoader__.load({
74
74
  "func.ui": "界面增强",
75
75
  "func.net": "网络接入",
76
76
  "func.infra": "系统底层",
77
- "func.other": "其他"
77
+ "func.other": "其他",
78
+
79
+ // 技术路线与版本兼容(v1.3.2)
80
+ "route.bundle": "自动激活",
81
+ "route.dual": "需登记",
82
+ "route.host": "纯宿主",
83
+ "route.local": "本地",
84
+ "route.unknown": "",
85
+ "route.hint.bundle": "自带 bundle 补丁:官方 CLI 安装后自动挂载激活行,无需手动登记",
86
+ "route.hint.dual": "无 bundle 补丁:需在补丁层登记激活行(插件管理器会自动登记)",
87
+ "route.hint.host": "纯宿主插件(无浏览器半)",
88
+ "route.hint.local": "本地 file:// 加载",
89
+ "verdict.ok": "✓ 适配",
90
+ "verdict.no": "⚠ 可能不兼容",
91
+ "verdict.unknown": "未声明版本",
92
+ "verdict.range": "声明 DSH:",
93
+ "tested.label": "实测",
94
+ "tested.title": "作者实测通过的 DSH 版本"
78
95
  };
79
96
 
80
97
  const en = {
@@ -132,7 +149,24 @@ window.__ModuleLoader__.load({
132
149
  "func.ui": "UI & Display",
133
150
  "func.net": "Network & Web",
134
151
  "func.infra": "Infrastructure",
135
- "func.other": "Other"
152
+ "func.other": "Other",
153
+
154
+ // route & verdict (v1.3.2)
155
+ "route.bundle": "auto-activate",
156
+ "route.dual": "needs row",
157
+ "route.host": "host-only",
158
+ "route.local": "local",
159
+ "route.unknown": "",
160
+ "route.hint.bundle": "Ships a bundle patch: the row mounts automatically on official CLI install",
161
+ "route.hint.dual": "No bundle patch: an activation row is required (the manager registers it)",
162
+ "route.hint.host": "Host-only plugin (no browser half)",
163
+ "route.hint.local": "Loaded via local file://",
164
+ "verdict.ok": "✓ compatible",
165
+ "verdict.no": "⚠ maybe incompatible",
166
+ "verdict.unknown": "no version declared",
167
+ "verdict.range": "Declared DSH:",
168
+ "tested.label": "tested",
169
+ "tested.title": "DSH versions the author tested"
136
170
  };
137
171
 
138
172
  /** 模块短名提取 */
@@ -208,6 +242,58 @@ window.__ModuleLoader__.load({
208
242
  { id: "disabled", labelKey: "status.disabled" }
209
243
  ];
210
244
 
245
+ /**
246
+ * 技术路线徽章配色(bundle=绿 / dual=橙 / host=蓝 / local=紫)
247
+ */
248
+ function routeColor(route) {
249
+ switch (route) {
250
+ case "bundle": return { fg: "#1a7f37", bg: "rgba(26,127,55,0.12)" };
251
+ case "dual": return { fg: "#bc4c00", bg: "rgba(188,76,0,0.13)" };
252
+ case "host": return { fg: "#0a66c2", bg: "rgba(10,102,194,0.12)" };
253
+ case "local": return { fg: "#8250df", bg: "rgba(130,80,223,0.12)" };
254
+ default: return { fg: "var(--dsw-alias-label-tertiary)", bg: "var(--dsw-alias-bg-layer-1)" };
255
+ }
256
+ }
257
+
258
+ /** 版本兼容徽章配色(ok=绿 / no=红 / unknown=灰) */
259
+ function verdictColor(verdict) {
260
+ switch (verdict) {
261
+ case "ok": return { fg: "#1a7f37", bg: "rgba(26,127,55,0.12)" };
262
+ case "no": return { fg: "#cf222e", bg: "rgba(207,34,46,0.13)" };
263
+ default: return { fg: "var(--dsw-alias-label-tertiary)", bg: "var(--dsw-alias-bg-layer-1)" };
264
+ }
265
+ }
266
+
267
+ /** 路由+版本徽章小工具(市场搜索结果与已安装列表共用) */
268
+ function badgePair(t, pkg, options) {
269
+ return [
270
+ pkg.route && pkg.route !== "unknown" ? _jsx("span", {
271
+ title: t("route.hint." + pkg.route) || "",
272
+ style: {
273
+ fontSize: "11px",
274
+ padding: "1px 5px",
275
+ borderRadius: "4px",
276
+ color: routeColor(pkg.route).fg,
277
+ background: routeColor(pkg.route).bg,
278
+ border: "1px solid " + routeColor(pkg.route).bg
279
+ },
280
+ children: t("route." + pkg.route)
281
+ }, "r") : null,
282
+ pkg.verdict ? _jsx("span", {
283
+ title: pkg.dshRange ? `${t("verdict.range")} ${pkg.dshRange}` : t("verdict.noRange"),
284
+ style: {
285
+ fontSize: "11px",
286
+ padding: "1px 5px",
287
+ borderRadius: "4px",
288
+ color: verdictColor(pkg.verdict).fg,
289
+ background: verdictColor(pkg.verdict).bg,
290
+ border: "1px solid " + verdictColor(pkg.verdict).bg
291
+ },
292
+ children: t("verdict." + pkg.verdict)
293
+ }, "v") : null
294
+ ];
295
+ }
296
+
211
297
  /**
212
298
  * =========================================================================
213
299
  * 1. 插件管理标签页组件 (ManagerTab)
@@ -773,6 +859,25 @@ window.__ModuleLoader__.load({
773
859
  ]
774
860
  }),
775
861
 
862
+ // 技术路线 + 版本兼容徽章(v1.3.2)
863
+ entry.route || entry.verdict ? _jsxs("div", {
864
+ style: { display: "flex", alignItems: "center", gap: "6px", flexWrap: "wrap" },
865
+ children: [
866
+ ...badgePair(t, entry),
867
+ entry.tested && entry.tested.length ? _jsx("span", {
868
+ title: t("tested.title"),
869
+ style: {
870
+ fontSize: "11px",
871
+ color: "var(--dsw-alias-label-tertiary)",
872
+ background: "var(--dsw-alias-bg-layer-1)",
873
+ padding: "1px 5px",
874
+ borderRadius: "4px"
875
+ },
876
+ children: `${t("tested.label")} ${entry.tested.join(", ")}`
877
+ }) : null
878
+ ]
879
+ }) : null,
880
+
776
881
  // 标识与双分类标签
777
882
  _jsxs("div", {
778
883
  style: { display: "flex", alignItems: "center", justifyContent: "space-between", fontSize: "11px" },
@@ -1146,6 +1251,7 @@ window.__ModuleLoader__.load({
1146
1251
  },
1147
1252
  children: `v${pkg.version}`
1148
1253
  }),
1254
+ ...badgePair(t, pkg),
1149
1255
  pkg.date ? _jsx("span", {
1150
1256
  style: { fontSize: "11px", color: "var(--dsw-alias-label-tertiary)" },
1151
1257
  children: pkg.date.slice(0, 10)
package/lib/index.mjs CHANGED
@@ -15,7 +15,7 @@ import { homedir } from 'node:os';
15
15
  export const name = 'dsh-plugin-manager-plus';
16
16
  export const inject = ['loader', 'webServer'];
17
17
 
18
- const PLUGIN_VERSION = '1.3.0';
18
+ const PLUGIN_VERSION = '1.3.2';
19
19
 
20
20
  /** 读取宿主 dsh 包版本(后端进程 argv[1] 即 dsh 的 bin.js,其上级即包根) */
21
21
  function readHostDshVersion() {
@@ -33,6 +33,174 @@ function readHostDshVersion() {
33
33
  }
34
34
  const HOST_DSH_VERSION = readHostDshVersion();
35
35
 
36
+ // =========================================================================
37
+ // 版本工具:社区插件的技术路线(route)与 DSH 版本兼容性(verdict)识别。
38
+ // 无第三方依赖,实现 npm semver 范围判断的实用子集(精确/^/~/>=/<=/>/</
39
+ // | |/x 通配/空格且),对无法解析的声明一律判 unknown 而非误报。
40
+ // =========================================================================
41
+
42
+ /** 解析 '1.2.3-alpha.1' → {major, minor, patch, pre};失败返回 null */
43
+ function parseSemver(v) {
44
+ const m = /^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z.-]+))?/.exec(String(v ?? '').trim());
45
+ if (!m) return null;
46
+ return { major: +m[1] || 0, minor: +m[2] || 0, patch: +m[3] || 0, pre: m[4] || '' };
47
+ }
48
+
49
+ /** 比较两个已解析版本:a > b → 1,a < b → -1,相等 → 0 */
50
+ function compareSemver(a, b) {
51
+ if (!a || !b) return 0;
52
+ for (const k of ['major', 'minor', 'patch']) {
53
+ if (a[k] !== b[k]) return a[k] > b[k] ? 1 : -1;
54
+ }
55
+ if (a.pre === b.pre) return 0;
56
+ if (!a.pre) return 1; // 正式版 > pre-release
57
+ if (!b.pre) return -1;
58
+ const pa = a.pre.split(/[.-]/).map(s => (/^\d+$/.test(s) ? Number(s) : s));
59
+ const pb = b.pre.split(/[.-]/).map(s => (/^\d+$/.test(s) ? Number(s) : s));
60
+ for (let i = 0; i < Math.max(pa.length, pb.length); i++) {
61
+ const x = pa[i], y = pb[i];
62
+ if (x === undefined) return -1;
63
+ if (y === undefined) return 1;
64
+ if (x === y) continue;
65
+ if (typeof x === 'number' && typeof y === 'number') return x > y ? 1 : -1;
66
+ if (typeof x === 'number') return 1; // 数字段 < 字母段(npm 规则相反,此处从简)
67
+ if (typeof y === 'number') return -1;
68
+ return x > y ? 1 : -1;
69
+ }
70
+ return 0;
71
+ }
72
+
73
+ /** 单个比较器(>=、^、~、=、精确、x 通配)是否满足 */
74
+ function cmpSatisfies(v, cmpRaw) {
75
+ const vp = parseSemver(v);
76
+ if (!vp) return false;
77
+ let m = /^(>=|<=|>|<|\^|~|=)?\s*(.*)$/.exec(String(cmpRaw || '').trim());
78
+ const op = m[1] || '=';
79
+ let spec = (m[2] || '').trim();
80
+ if (!spec || spec === '*' || spec === 'x' || spec === 'X') return true;
81
+ // x 通配(如 1.x、1.2.x)
82
+ if (/^(\d+)(?:\.(\d+))?\.x$/i.test(spec) || /^(\d+)$/.test(spec) && op === '=' || /^(\d+)\.(\d+)$/.test(spec) && op === '=') {
83
+ const parts = spec.split('.');
84
+ const wantMaj = +parts[0];
85
+ if (vp.major !== wantMaj) return false;
86
+ if (parts.length >= 2 && parts[1] !== 'x' && parts[1] !== 'X' && vp.minor !== +parts[1]) return false;
87
+ if (parts.length >= 3 && parts[2] !== 'x' && parts[2] !== 'X' && vp.patch !== +parts[2]) return false;
88
+ if (op === '>') return false; // 对 x 通配不做精细上界,从宽
89
+ return true;
90
+ }
91
+ // 处理组合范围如 ">=1.2.3 <2.0.0"(外层已按空格拆分)
92
+ const sp = parseSemver(spec);
93
+ if (!sp) return false;
94
+ switch (op) {
95
+ case '=': return compareSemver(vp, sp) === 0;
96
+ case '>': return compareSemver(vp, sp) > 0;
97
+ case '<': return compareSemver(vp, sp) < 0;
98
+ case '>=': return compareSemver(vp, sp) >= 0;
99
+ case '<=': return compareSemver(vp, sp) <= 0;
100
+ case '~': return vp.major === sp.major && vp.minor === sp.minor && compareSemver(vp, sp) >= 0;
101
+ case '^': {
102
+ const lower = compareSemver(vp, sp) >= 0;
103
+ if (!lower) return false;
104
+ if (sp.major > 0) return vp.major === sp.major;
105
+ if (sp.minor > 0) return vp.minor === sp.minor; // 0.x.y 系列
106
+ return vp.patch === sp.patch; // 0.0.x
107
+ }
108
+ default: return false;
109
+ }
110
+ }
111
+
112
+ /** 版本是否满足范围(支持 || 与空格分隔的多个比较器) */
113
+ function versionSatisfies(version, range) {
114
+ const r = String(range ?? '').trim();
115
+ if (!r || r === '*' || r === 'latest' || /^[a-zA-Z]/.test(r)) return true; // tag/无约束
116
+ const v = parseSemver(version);
117
+ if (!v) return false;
118
+ for (const or of r.split('||')) {
119
+ const parts = or.trim().split(/\s+/).filter(Boolean);
120
+ if (parts.length === 0) continue;
121
+ let ok = true;
122
+ for (const part of parts) {
123
+ if (part !== '' && !cmpSatisfies(version, part)) { ok = false; break; }
124
+ }
125
+ if (ok) return true;
126
+ }
127
+ return false;
128
+ }
129
+
130
+ /**
131
+ * 提取包的 DSH 版本约束(多个声明源,按优先级取首个非空)。
132
+ * 0 = dsh.compatibility.dsh(自定义声明,最明确)
133
+ * 1 = peerDependencies['@deepseek-ai/dsh']
134
+ * 2 = peerDependencies['@deepseek-ai/dsh-settings'](dshmarket 风格)
135
+ * 3 = peerDependencies['@deepseek-ai/cordis'](仅作参考,不判 verdict)
136
+ */
137
+ function extractDshRange(pkg) {
138
+ if (!pkg || typeof pkg !== 'object') return null;
139
+ if (pkg.dsh?.compatibility?.dsh) return String(pkg.dsh.compatibility.dsh);
140
+ const peer = pkg.peerDependencies || {};
141
+ if (peer['@deepseek-ai/dsh']) return String(peer['@deepseek-ai/dsh']);
142
+ if (peer['@deepseek-ai/dsh-settings']) return String(peer['@deepseek-ai/dsh-settings']);
143
+ return null;
144
+ }
145
+
146
+ /**
147
+ * 判定社区插件安装技术路线:
148
+ * 'bundle' — 带 dsh.bundle.patch,官方 CLI add 后 bundle 层自动挂载激活行,
149
+ * 插件管理器安装时**不写**家级行(写则双源致命错误)
150
+ * 'dual' — 有 dsh.client(浏览器半)但无 bundle patch,需在 patch 层登记
151
+ * 激活行(裸包名或 file:// + client 行),插件管理器会登记
152
+ * 'host' — 纯宿主插件(无 dsh.client),仅需宿主激活行
153
+ * 'local' — file:// 本地加载(非 npm 包)
154
+ */
155
+ function detectRoute(moduleName, pkg) {
156
+ if (typeof moduleName === 'string' && moduleName.startsWith('file://')) return 'local';
157
+ if (pkg?.dsh?.bundle?.patch) return 'bundle';
158
+ if (pkg?.dsh?.client?.platform) return 'dual';
159
+ if (pkg && typeof pkg === 'object') return 'host';
160
+ return 'unknown';
161
+ }
162
+
163
+ /**
164
+ * 组装某包的路线 + 版本兼容信息。
165
+ * @param {string} moduleName 加载 specifier(可能为 file:// URL)
166
+ * @param {object|null} pkg 已解析的 package.json(无则为 null)
167
+ * @returns {{route, dshRange, tested, enginesNode, verdict}}
168
+ */
169
+ function assessPackage(moduleName, pkg) {
170
+ const route = detectRoute(moduleName, pkg);
171
+ const tested = pkg?.dsh?.compatibility?.tested?.length
172
+ ? pkg.dsh.compatibility.tested.map(String)
173
+ : null;
174
+ const enginesNode = pkg?.engines?.node ? String(pkg.engines.node) : null;
175
+ const dshRange = extractDshRange(pkg);
176
+ let verdict = 'unknown';
177
+ if (dshRange && HOST_DSH_VERSION) {
178
+ verdict = versionSatisfies(HOST_DSH_VERSION, dshRange) ? 'ok' : 'no';
179
+ }
180
+ return { route, dshRange, tested, enginesNode, verdict };
181
+ }
182
+
183
+ /** 从已安装目录读取包 metadata(读不到返回 null);支持 file:// 本地插件向上找 package.json */
184
+ function readInstalledPkg(moduleName) {
185
+ try {
186
+ if (typeof moduleName === 'string' && moduleName.startsWith('file://')) {
187
+ let dir = dirname(fileURLToPath(moduleName.split('?')[0]));
188
+ for (let i = 0; i < 4; i++) {
189
+ const cand = join(dir, 'package.json');
190
+ if (existsSync(cand)) return JSON.parse(readFileSync(cand, 'utf8'));
191
+ const parent = dirname(dir);
192
+ if (parent === dir) break;
193
+ dir = parent;
194
+ }
195
+ return null;
196
+ }
197
+ if (!moduleName || moduleName.startsWith('@deepseek-ai/')) return null; // 官方 bundle 行不读
198
+ const p = join(WEB_PROFILE_DIR, 'node_modules', ...String(moduleName).split('/'), 'package.json');
199
+ if (!existsSync(p)) return null;
200
+ return JSON.parse(readFileSync(p, 'utf8'));
201
+ } catch { return null; }
202
+ }
203
+
36
204
  /** Fiber 状态常量映射 */
37
205
  const FIBER_PHASE = {
38
206
  0: 'pending',
@@ -935,7 +1103,11 @@ export function apply(ctx) {
935
1103
  if (pathname === '/plugin-manager/api/inventory' && req.method === 'GET') {
936
1104
  const { insertEntries, overrideEntries } = getHomePatchEntries();
937
1105
  const manifest = readManifest();
938
- const manifestEntryIds = new Set(manifest.map(m => m.entryId));
1106
+ const manifestEntryIds = new Set();
1107
+ for (const m of manifest) {
1108
+ manifestEntryIds.add(m.entryId);
1109
+ for (const rid of (m.bundleRowIds || [])) manifestEntryIds.add(rid);
1110
+ }
939
1111
 
940
1112
  const list = [];
941
1113
  const seenCleanIds = new Set();
@@ -965,6 +1137,9 @@ export function apply(ctx) {
965
1137
  const source = isHomeInsert ? 'home' : 'builtin';
966
1138
  const cat = determineCategory(source, cId || entryId, moduleName, manifestEntryIds);
967
1139
  const funcCat = determineFuncCategory(moduleName);
1140
+ // bundle 形态插件(行由包内 bundle 层挂载、经 manifest 跟踪)也可管理
1141
+ const manifestTracked = manifestEntryIds.has(cId || entryId);
1142
+ const assess = assessPackage(moduleName, readInstalledPkg(moduleName));
968
1143
 
969
1144
  list.push({
970
1145
  entryId: cId || entryId,
@@ -972,9 +1147,14 @@ export function apply(ctx) {
972
1147
  enabled,
973
1148
  phase: rawFiberPhase ?? 'unobserved',
974
1149
  source,
975
- manageable: isHomeInsert,
1150
+ manageable: isHomeInsert || manifestTracked,
976
1151
  category: cat,
977
- funcCategory: funcCat
1152
+ funcCategory: funcCat,
1153
+ route: assess.route,
1154
+ verdict: assess.verdict,
1155
+ dshRange: assess.dshRange,
1156
+ tested: assess.tested,
1157
+ enginesNode: assess.enginesNode
978
1158
  });
979
1159
  }
980
1160
  }
@@ -984,6 +1164,7 @@ export function apply(ctx) {
984
1164
  if (!seenCleanIds.has(id) && !seenCleanIds.has(cleanId(id))) {
985
1165
  const cat = determineCategory('home', id, item.name, manifestEntryIds);
986
1166
  const funcCat = determineFuncCategory(item.name);
1167
+ const assess = assessPackage(item.name, readInstalledPkg(item.name));
987
1168
 
988
1169
  list.push({
989
1170
  entryId: id,
@@ -993,7 +1174,12 @@ export function apply(ctx) {
993
1174
  source: 'home',
994
1175
  manageable: true,
995
1176
  category: cat,
996
- funcCategory: funcCat
1177
+ funcCategory: funcCat,
1178
+ route: assess.route,
1179
+ verdict: assess.verdict,
1180
+ dshRange: assess.dshRange,
1181
+ tested: assess.tested,
1182
+ enginesNode: assess.enginesNode
997
1183
  });
998
1184
  }
999
1185
  }
@@ -1030,15 +1216,41 @@ export function apply(ctx) {
1030
1216
  }
1031
1217
 
1032
1218
  const rawResults = data?.objects || [];
1219
+ // 并发拉取候选包 metadata(npmmirror 快),用于判定技术路线与版本兼容;
1220
+ // 单个失败不影响整体(unknown 兜底)。
1221
+ const metaBy = new Map();
1222
+ const NAMES = rawResults.map(i => i.package?.name).filter(Boolean).slice(0, 25);
1223
+ await Promise.all(NAMES.map(async (n) => {
1224
+ try {
1225
+ const enc = n.split('/').map(encodeURIComponent).join('/');
1226
+ const resp = await fetch(`https://registry.npmmirror.com/${enc}`, {
1227
+ signal: AbortSignal.timeout(6000)
1228
+ });
1229
+ if (resp.ok) {
1230
+ const meta = await resp.json();
1231
+ const latest = meta['dist-tags']?.latest || meta.version;
1232
+ const ver = meta.versions?.[latest] || meta;
1233
+ metaBy.set(n, ver);
1234
+ }
1235
+ } catch {}
1236
+ }));
1237
+
1033
1238
  const results = rawResults.map((item) => {
1034
1239
  const pkg = item.package || {};
1035
1240
  const isInstalled = installedNames.has(pkg.name);
1241
+ const meta = metaBy.get(pkg.name) || null;
1242
+ const assess = assessPackage(pkg.name, meta);
1036
1243
  return {
1037
1244
  name: pkg.name,
1038
1245
  version: pkg.version,
1039
1246
  description: pkg.description || '',
1040
1247
  date: pkg.date || '',
1041
- installed: isInstalled
1248
+ installed: isInstalled,
1249
+ route: assess.route,
1250
+ verdict: assess.verdict,
1251
+ dshRange: assess.dshRange,
1252
+ tested: assess.tested,
1253
+ enginesNode: assess.enginesNode
1042
1254
  };
1043
1255
  });
1044
1256
 
@@ -1079,26 +1291,50 @@ export function apply(ctx) {
1079
1291
  }
1080
1292
 
1081
1293
  let installedVersion = version || '';
1294
+ let isBundle = false;
1295
+ let bundleRowIds = [];
1082
1296
  try {
1083
1297
  const installedPkg = JSON.parse(readFileSync(targetPkgJsonPath, 'utf8'));
1084
1298
  installedVersion = installedPkg.version || installedVersion;
1299
+ // 探测 bundle 形态:包内 dsh.bundle.patch 由官方 CLI 自动挂载为
1300
+ // profile 的 bundle 层,行随包安装/卸载自动存在/消失——此时
1301
+ // **不得**再写家级插入行(同包双 Loader 源 = 致命错误)。
1302
+ const bundlePatchRel = installedPkg?.dsh?.bundle?.patch;
1303
+ if (typeof bundlePatchRel === 'string' && bundlePatchRel) {
1304
+ const bundlePatchPath = join(WEB_PROFILE_DIR, 'node_modules', ...pkgName.split('/'), bundlePatchRel.replace(/^\.\//, ''));
1305
+ if (existsSync(bundlePatchPath)) {
1306
+ const { blocks } = parsePatchYaml(readFileSync(bundlePatchPath, 'utf8'));
1307
+ for (const b of blocks) {
1308
+ if (b.type === 'insert') for (const e of b.entries) bundleRowIds.push(e.id);
1309
+ }
1310
+ }
1311
+ if (bundleRowIds.length > 0) isBundle = true;
1312
+ }
1085
1313
  } catch {}
1086
1314
 
1087
- // 更新 Patch 文件
1088
- patchAddInstallEntry(pkgName, entryId);
1315
+ if (!isBundle) {
1316
+ // 非 bundle 形态:包装上没有激活行,需要插件管理器在家级补丁登记激活
1317
+ patchAddInstallEntry(pkgName, entryId);
1318
+ logger.info(`插件 ${pkgSpec} 已登记至家级补丁 (${entryId})`);
1319
+ } else {
1320
+ // bundle 形态:bundle 层已自动挂载激活行,绝不写家级插入行
1321
+ logger.info(`插件 ${pkgSpec} 为 bundle 形态(bundle 层行 id: ${bundleRowIds.join(', ')}),跳过家级登记(避免双 Loader 源)`);
1322
+ }
1089
1323
 
1090
1324
  // 更新 Manifest 记录
1091
1325
  const manifest = readManifest();
1092
- const filtered = manifest.filter(m => m.entryId !== entryId);
1326
+ const filtered = manifest.filter(m => m.entryId !== entryId && !(m.bundleRowIds || []).includes(entryId));
1093
1327
  filtered.push({
1094
1328
  entryId,
1095
1329
  packageName: pkgName,
1096
1330
  version: installedVersion,
1097
- installedAt: new Date().toISOString()
1331
+ installedAt: new Date().toISOString(),
1332
+ bundle: isBundle,
1333
+ bundleRowIds
1098
1334
  });
1099
1335
  saveManifest(filtered);
1100
1336
 
1101
- logger.info(`插件 ${pkgSpec} 安装成功并已登记至 cordis.patch.yml`);
1337
+ logger.info(`插件 ${pkgSpec} 安装成功${isBundle ? '(bundle 形态,无家级登记)' : '并已登记至 cordis.patch.yml'}`);
1102
1338
  return { ok: true, version: installedVersion, log: installRes.log };
1103
1339
  });
1104
1340
 
@@ -1131,7 +1367,7 @@ export function apply(ctx) {
1131
1367
  const { insertEntries } = getHomePatchEntries();
1132
1368
  const homeEntry = insertEntries.get(entryId);
1133
1369
  const manifest = readManifest();
1134
- const manifestRecord = manifest.find(m => m.entryId === entryId);
1370
+ const manifestRecord = manifest.find(m => m.entryId === entryId || (m.bundleRowIds || []).includes(entryId));
1135
1371
 
1136
1372
  // 1. 由插件管理器安装的 npm 包
1137
1373
  if (manifestRecord) {
@@ -1225,3 +1461,7 @@ export function apply(ctx) {
1225
1461
  mountRoutes(ctx);
1226
1462
  }
1227
1463
  }
1464
+
1465
+ // 工具函数导出(供单测/外部复用;cordis 插件加载仅取 name/inject/apply,额外导出无害)
1466
+ export { versionSatisfies, extractDshRange, assessPackage, parseSemver };
1467
+
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "dsh-plugin-manager-plus",
3
3
  "description": "DeepSeek Harness (DSH) 插件管理器:设置面板内的社区插件市场(npm 搜索一键安装)+ 已安装插件管理(来源/用途/状态三维筛选、启停热重载、一键卸载、持久化)。Plugin manager & marketplace for DeepSeek Harness.",
4
- "version": "1.3.0",
4
+ "version": "1.3.2",
5
5
  "type": "module",
6
6
  "main": "./lib/index.mjs",
7
7
  "exports": {
@@ -52,4 +52,4 @@
52
52
  "url": "git+https://github.com/new-256/dsh-plugin-manager.git"
53
53
  },
54
54
  "license": "MIT"
55
- }
55
+ }