dsh-skill-browser 1.9.10 → 2.0.0

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 (52) hide show
  1. package/CHANGELOG.md +31 -0
  2. package/README.md +80 -81
  3. package/docs/images/panel-browse.png +0 -0
  4. package/docs/images/panel-ledger.png +0 -0
  5. package/lib/client.js +1381 -269
  6. package/lib/client.js.bak-6colimp-20261028-230915 +3748 -0
  7. package/lib/client.js.bak-auth12-20261029-020107 +3749 -0
  8. package/lib/client.js.bak-auth12b-20261029-020140 +3749 -0
  9. package/lib/client.js.bak-auth12c-20261029-020206 +3749 -0
  10. package/lib/client.js.bak-auth12d-20261029-020232 +3747 -0
  11. package/lib/client.js.bak-auto-20261028-171351 +3669 -0
  12. package/lib/client.js.bak-auto2-20261028-171422 +3676 -0
  13. package/lib/client.js.bak-auto3-20261028-171545 +3676 -0
  14. package/lib/client.js.bak-followball-20261028-224743 +3720 -0
  15. package/lib/client.js.bak-gearico-20261028-230937 +3748 -0
  16. package/lib/client.js.bak-glow-20261030 +3676 -0
  17. package/lib/client.js.bak-grid-20261028-220142 +3680 -0
  18. package/lib/client.js.bak-guide-20261029-003108 +3750 -0
  19. package/lib/client.js.bak-guide2-20261029-003213 +3751 -0
  20. package/lib/client.js.bak-guide3-20261029-003236 +3763 -0
  21. package/lib/client.js.bak-guide4-20261029-003249 +3768 -0
  22. package/lib/client.js.bak-guidefc-20261029-003535 +3775 -0
  23. package/lib/client.js.bak-guideico-20261029-013809 +3776 -0
  24. package/lib/client.js.bak-guideorder-20261029-004345 +3775 -0
  25. package/lib/client.js.bak-headbtn-20261028-232114 +3750 -0
  26. package/lib/client.js.bak-menu6col-20261028-225126 +3741 -0
  27. package/lib/client.js.bak-menu6col2-20261028-225348 +3741 -0
  28. package/lib/client.js.bak-menu6col2-20261028-225527 +3741 -0
  29. package/lib/client.js.bak-monobtn-20261028-230502 +3741 -0
  30. package/lib/client.js.bak-navsimple-20261029-015235 +3786 -0
  31. package/lib/client.js.bak-nobadge-20261029-015354 +3784 -0
  32. package/lib/client.js.bak-nodrag-20261028-170713 +3651 -0
  33. package/lib/client.js.bak-official-20261028-171240 +3653 -0
  34. package/lib/client.js.bak-official2-20261028-171304 +3668 -0
  35. package/lib/client.js.bak-renumber-20261028-215518 +3681 -0
  36. package/lib/client.js.bak-sbgap-20261029-013144 +3776 -0
  37. package/lib/client.js.bak-sbgap-20261029-013202 +3776 -0
  38. package/lib/client.js.bak-sbrowh-20261029-012307 +3776 -0
  39. package/lib/client.js.bak-sbrowh-20261029-012333 +3776 -0
  40. package/lib/client.js.bak-scope-sb-20261029-011711 +3776 -0
  41. package/lib/client.js.bak-skinclarity-20261029-011027 +3776 -0
  42. package/lib/client.js.bak-skincols-20261028-224614 +3720 -0
  43. package/lib/client.js.bak-star-20261028-231205 +3748 -0
  44. package/lib/client.js.bak-star-20261028-231233 +3748 -0
  45. package/lib/client.js.bak-sync-20261028-220538 +3680 -0
  46. package/lib/groups.js +171 -21
  47. package/lib/index.js +141 -2
  48. package/lib/mcp.js +853 -0
  49. package/locale/en.json +2 -2
  50. package/locale/zh.json +2 -2
  51. package/package.json +87 -87
  52. package/docs/images/skill-browser-panel.png +0 -0
package/lib/mcp.js ADDED
@@ -0,0 +1,853 @@
1
+ // dsh-skill-browser - MCP 管理子模块(宿主端,v2.0.0)
2
+ //
3
+ // 只读优先:枚举运行时 loader 里每一个 @deepseek-ai/dsh-mcp-client 条目,
4
+ // 给出真实状态(Cordis FiberState)、已注册工具清单、自动功能概括、
5
+ // 调用统计(监听官方 tools/result 事件,落盘 jsonl)。
6
+ //
7
+ // 启停(P1):**不改写整份 YAML**(那会丢光 344KB 中文注释)。只在用户
8
+ // profile 的 cordis.patch.yml 末尾维护一个带 BEGIN/END 标记的「受管覆盖块」,
9
+ // 纯文本追加/替换,幂等;DSH 的 user patch layer 本身支持 HMR 热重载,
10
+ // 因此改完即热应用,重启后同样保留。
11
+ //
12
+ // 重连(P1):直接走 Cordis Fiber 的内存重启(entry.fiber.update(config,true)),
13
+ // 不触碰任何文件。
14
+ //
15
+ // 导出(P0):把运行时配置反解为标准 mcpServers JSON(Claude Desktop / Cursor 同构)。
16
+
17
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, appendFileSync, copyFileSync, readdirSync, unlinkSync, statSync } from "node:fs";
18
+ import { join, dirname, basename } from "node:path";
19
+ import { homedir } from "node:os";
20
+ import { askModel } from "./groups.js";
21
+
22
+ var MCP_PKG = "@deepseek-ai/dsh-mcp-client";
23
+
24
+ // Cordis FiberState 常量镜像(与 @deepseek-ai/cordis 的 const enum 对应)
25
+ var FIBER_STATE = { 0: "pending", 1: "loading", 2: "active", 3: "failed", 4: "disposed", 5: "unloading" };
26
+
27
+ var MARK_BEGIN = "# ==== dsh-skill-browser:mcp-managed BEGIN (auto-generated, DO NOT EDIT) ====";
28
+ var MARK_END = "# ==== dsh-skill-browser:mcp-managed END ====";
29
+
30
+ var SENSITIVE_RE = /(token|key|secret|password|passwd|pwd|credential|auth|api[-_]?key)/i;
31
+
32
+ function dataDir() { return join(homedir(), ".dsh", "data", "dsh-skill-browser"); }
33
+ export function mcpStatsFile() { return join(dataDir(), "mcp-calls.jsonl"); }
34
+ function backupDir() { return join(homedir(), ".dsh", "backups"); }
35
+
36
+ function maskValue(v) {
37
+ var s = String(v == null ? "" : v);
38
+ if (!s) return "";
39
+ if (s.length <= 6) return "••••";
40
+ return s.slice(0, 2) + "••••" + s.slice(-2);
41
+ }
42
+
43
+ // ─────────────────────────── 枚举 ───────────────────────────
44
+
45
+ function fiberPhase(entry) {
46
+ try {
47
+ var f = entry.fiber;
48
+ if (!f) return { num: null, name: "idle" };
49
+ var n = typeof f.state === "number" ? f.state : null;
50
+ return { num: n, name: n === null ? "unknown" : (FIBER_STATE[n] || "unknown") };
51
+ } catch (e) { return { num: null, name: "unknown" }; }
52
+ }
53
+
54
+ function safeToolSchemas(ctx) {
55
+ // tools 是可选服务:直接 ctx.tools 未 inject 会抛「cannot get property without inject」,
56
+ // 故统一走 ctx.get("tools")(与 groups.js 的 ctx.get("llm") 同一契约)。
57
+ var tools = null;
58
+ try { if (typeof ctx.get === "function") tools = ctx.get("tools") || null; } catch (e) { tools = null; }
59
+ if (!tools) { try { if (ctx.tools) tools = ctx.tools; } catch (e) { tools = null; } }
60
+ if (!tools) return [];
61
+ // 性能:优先 definitions()(只读引用,不做 parameters 深拷贝),
62
+ // schemas() 会对每个工具 projectToolSchema 深拷贝参数字典,工具多时开销大。
63
+ try {
64
+ if (typeof tools.definitions === "function") {
65
+ var d = tools.definitions();
66
+ if (Array.isArray(d)) return d;
67
+ }
68
+ } catch (e) { /* ignore */ }
69
+ try {
70
+ if (typeof tools.schemas === "function") {
71
+ var s = tools.schemas();
72
+ if (Array.isArray(s)) return s;
73
+ }
74
+ } catch (e) { /* ignore */ }
75
+ return [];
76
+ }
77
+
78
+ function pickServerTools(allTools, serverName) {
79
+ var prefix = "mcp__" + serverName + "__";
80
+ var out = [];
81
+ for (var i = 0; i < allTools.length; i++) {
82
+ var t = allTools[i];
83
+ if (!t || typeof t.name !== "string") continue;
84
+ var hit = t.name.indexOf(prefix) === 0;
85
+ if (!hit) {
86
+ // 兜底:hash 或字符替换后仍按第二段识别
87
+ var parts = t.name.split("__");
88
+ if (parts.length >= 3 && parts[0] === "mcp" && parts[1] === serverName) hit = true;
89
+ }
90
+ if (!hit) continue;
91
+ out.push({ name: t.name, rawName: t.name.slice(prefix.length) || t.name, description: (t.description || "").slice(0, 300) });
92
+ }
93
+ return out;
94
+ }
95
+
96
+ // 版本推断:MCP 协议 serverInfo.version 对第三方插件不可见(client 实例在
97
+ // mcp-client 内部闭包里,不对外暴露),只能从启动配置里推断:command /
98
+ // args / url 中形如 x.y.z 的版本串,取最后一个(路径越靠后越像版本目录,
99
+ // 例:...\toolchain\0.8.8\windows-mcp\...\windows-mcp.exe → 0.8.8)。
100
+ export function guessVersion(cfg) {
101
+ try {
102
+ var hay = [cfg.command || "", Array.isArray(cfg.args) ? cfg.args.join(" ") : "", cfg.url || ""].join(" ");
103
+ var m = hay.match(/(\d+\.\d+\.\d+)/g);
104
+ return m && m.length ? m[m.length - 1] : "";
105
+ } catch (e) { return ""; }
106
+ }
107
+
108
+ // 自动功能概括:从工具名与描述提炼。
109
+ function summarizeTools(tools) {
110
+ if (!tools.length) return "未连接或尚未注册工具";
111
+ var kinds = {
112
+ query: /(get|list|read|query|fetch|search|detect|status|info|inspect|show|find|describe|check|watch|stat)/i,
113
+ exec: /(run|execute|launch|start|eval|invoke|call|submit|apply|solve|iterate|compile|build)/i,
114
+ write: /(set|write|update|patch|edit|create|add|insert|put|upload|save|modify|enable|disable|toggle)/i,
115
+ delete: /(delete|remove|drop|clear|reset|kill|close|cancel|stop|abort)/i,
116
+ export: /(export|download|dump|snapshot|serialize|backup)/i,
117
+ file: /(file|path|dir|folder|disk|fs|upload|download)/i,
118
+ session: /(session|connect|disconnect|reconnect|auth|login)/i
119
+ };
120
+ var buckets = { query: 0, exec: 0, write: 0, delete: 0, export: 0, file: 0, session: 0 };
121
+ tools.forEach(function (t) {
122
+ var s = t.rawName + " " + (t.description || "");
123
+ Object.keys(kinds).forEach(function (k) { if (kinds[k].test(s)) buckets[k]++; });
124
+ });
125
+ var labels = { query: "查询", exec: "执行", write: "写入/配置", delete: "删除/停止", export: "导出", file: "文件", session: "会话/连接" };
126
+ var picked = Object.keys(buckets)
127
+ .filter(function (k) { return buckets[k] > 0; })
128
+ .sort(function (a, b) { return buckets[b] - buckets[a]; })
129
+ .slice(0, 3)
130
+ .map(function (k) { return labels[k]; });
131
+ var head = tools.length + " 个工具";
132
+ if (!picked.length) return head;
133
+ return head + ",主要提供" + picked.join(" / ") + "能力";
134
+ }
135
+
136
+ function findOwnerFile(node) {
137
+ var cur = node;
138
+ for (var i = 0; i < 30 && cur; i++) {
139
+ try {
140
+ if (typeof cur.filename === "string" && cur.filename) return cur.filename;
141
+ } catch (e) { /* ignore */ }
142
+ var next = null;
143
+ try {
144
+ if (cur.ctx && cur.ctx.fiber && cur.ctx.fiber.entry) next = cur.ctx.fiber.entry.parent;
145
+ } catch (e) { next = null; }
146
+ if (!next || next === cur) break;
147
+ cur = next;
148
+ }
149
+ return null;
150
+ }
151
+
152
+ // 兜底:在各 profile 的 cordis.patch.yml 里找含该 entry id 的文件
153
+ function fallbackPatchFile(entryId) {
154
+ try {
155
+ var roots = [];
156
+ if (process.env.DSH_HOME) roots.push(join(process.env.DSH_HOME, "profiles"));
157
+ else roots.push(join(homedir(), ".dsh", "profiles"));
158
+ for (var r = 0; r < roots.length; r++) {
159
+ var base = roots[r];
160
+ if (!existsSync(base)) continue;
161
+ var names = readdirSync(base);
162
+ for (var i = 0; i < names.length; i++) {
163
+ var f = join(base, names[i], "cordis.patch.yml");
164
+ if (!existsSync(f)) continue;
165
+ try {
166
+ var txt = readFileSync(f, "utf8");
167
+ if (txt.indexOf(MCP_PKG) >= 0 && (!entryId || txt.indexOf(entryId) >= 0)) return f;
168
+ } catch (e) { /* ignore */ }
169
+ }
170
+ }
171
+ } catch (e) { /* ignore */ }
172
+ return null;
173
+ }
174
+
175
+ // 校验某个文件是否确实承载了该条目(root include 的 filename 理论上就是
176
+ // profile 的 cordis.patch.yml,但不同启动路径下可能是 cordis.yml,故必须核实)。
177
+ function safeContains(file, needle) {
178
+ try {
179
+ if (!file || !existsSync(file)) return false;
180
+ return readFileSync(file, "utf8").indexOf(String(needle)) >= 0;
181
+ } catch (e) { return false; }
182
+ }
183
+
184
+ function resolveEntryFile(entry, id) {
185
+ var f = findOwnerFile(entry && entry.parent);
186
+ if (f && safeContains(f, id)) return f;
187
+ return fallbackPatchFile(id);
188
+ }
189
+
190
+ var _mcpListCache = { ts: 0, data: null };
191
+ export function collectMcp(ctx, force) {
192
+ var _now = Date.now();
193
+ if (!force && _mcpListCache.data && (_now - _mcpListCache.ts) < 1500) return _mcpListCache.data;
194
+ var allTools = safeToolSchemas(ctx);
195
+ var servers = [];
196
+ var seen = {};
197
+ var entries = [];
198
+ try {
199
+ for (var row of ctx.loader.entries()) entries.push(row);
200
+ } catch (e) { /* loader 不可用 */ }
201
+
202
+ for (var i = 0; i < entries.length; i++) {
203
+ var entry = entries[i];
204
+ var opt = entry && entry.options ? entry.options : null;
205
+ if (!opt || opt.group) continue;
206
+ if (opt.name !== MCP_PKG) continue;
207
+ var cfg = opt.config || {};
208
+ var id = String(opt.id || "");
209
+ if (seen[id]) continue;
210
+ seen[id] = true;
211
+
212
+ var serverName = typeof cfg.serverName === "string" && cfg.serverName ? cfg.serverName : id;
213
+ var transport = cfg.transport || (cfg.url ? "streamable-http" : "stdio");
214
+ var disabled = false;
215
+ try { disabled = !!entry.disabled; } catch (e) { disabled = !!opt.disabled; }
216
+ var phase = fiberPhase(entry);
217
+ if (disabled) phase = { num: phase.num, name: "disabled" };
218
+
219
+ var tools = pickServerTools(allTools, serverName);
220
+
221
+ // env:只回键名 + 打码值(UI 展示用),真实值只在导出接口里给
222
+ var envKeys = [];
223
+ var envMasked = {};
224
+ if (cfg.env && typeof cfg.env === "object") {
225
+ Object.keys(cfg.env).forEach(function (k) {
226
+ envKeys.push(k);
227
+ envMasked[k] = SENSITIVE_RE.test(k) ? maskValue(cfg.env[k]) : String(cfg.env[k] == null ? "" : cfg.env[k]);
228
+ });
229
+ }
230
+ var headersMasked = {};
231
+ if (cfg.headers && typeof cfg.headers === "object") {
232
+ Object.keys(cfg.headers).forEach(function (k) { headersMasked[k] = SENSITIVE_RE.test(k) ? maskValue(cfg.headers[k]) : String(cfg.headers[k]); });
233
+ }
234
+
235
+ var patchFile = resolveEntryFile(entry, id);
236
+
237
+ servers.push({
238
+ id: id,
239
+ serverName: serverName,
240
+ transport: transport,
241
+ version: guessVersion(cfg),
242
+ command: cfg.command || "",
243
+ args: Array.isArray(cfg.args) ? cfg.args : [],
244
+ cwd: cfg.cwd || "",
245
+ url: cfg.url || "",
246
+ envKeys: envKeys,
247
+ env: envMasked,
248
+ headers: headersMasked,
249
+ disabled: disabled,
250
+ phase: phase.name,
251
+ phaseNum: phase.num,
252
+ toolCount: tools.length,
253
+ tools: tools,
254
+ summary: summarizeTools(tools),
255
+ patchFile: patchFile || "",
256
+ failOnStartupError: cfg.failOnStartupError,
257
+ toolCallTimeoutMs: cfg.toolCallTimeoutMs
258
+ });
259
+ }
260
+
261
+ servers.sort(function (a, b) { return String(a.serverName).localeCompare(String(b.serverName)); });
262
+ // 诊断日志(低频:每次面板刷新一次):一眼看清枚举到的服务器与其工具数。
263
+ try {
264
+ if (ctx && ctx.logger && typeof ctx.logger.info === "function") {
265
+ var toolTotal = servers.reduce(function (a, s) { return a + (s.toolCount || 0); }, 0);
266
+ var detail = servers.map(function (s) { return s.serverName + ":" + s.phase + ":" + s.toolCount; }).join(", ");
267
+ ctx.logger.info("dsh-skill-browser: mcp collect total=" + servers.length + " tools=" + toolTotal + (detail ? " [" + detail + "]" : ""));
268
+ }
269
+ } catch (e0) { /* 日志失败不影响返回 */ }
270
+ var _result = {
271
+ ok: true,
272
+ ts: Date.now(),
273
+ total: servers.length,
274
+ active: servers.filter(function (s) { return s.phase === "active"; }).length,
275
+ servers: servers
276
+ };
277
+ _mcpListCache = { ts: _now, data: _result };
278
+ return _result;
279
+ }
280
+
281
+ // ── 本机 MCP 快速扫描(只读):找出「已配置」与「本机已安装包」的所有 MCP ──
282
+ // 目的:给 MCP 界面的 AI「自动获取 / 修复」提供真实数据源,绝不靠记忆猜。
283
+ export function scanLocalMcp(ctx) {
284
+ var conf = collectMcp(ctx, true);
285
+ var servers = (conf && conf.servers) || [];
286
+ var configuredNames = {};
287
+ var configuredHints = {};
288
+ servers.forEach(function (s) {
289
+ configuredNames[String(s.serverName)] = true;
290
+ var txt = [s.command].concat(s.args || []).join(" ");
291
+ var m = txt.match(/(@?[a-z0-9._\-\/]*mcp[a-z0-9._\-]*)/i);
292
+ if (m) configuredHints[m[1].toLowerCase()] = true;
293
+ });
294
+
295
+ var home = homedir();
296
+ var roots = [];
297
+ try {
298
+ var profsDir = join(home, ".dsh", "profiles");
299
+ if (existsSync(profsDir)) readdirSync(profsDir).forEach(function (p) { roots.push(join(profsDir, p, "node_modules")); });
300
+ } catch (e) { }
301
+ try {
302
+ var lp = join(home, ".dsh", "local-plugins");
303
+ if (existsSync(lp)) readdirSync(lp).forEach(function (p) { roots.push(join(lp, p, "node_modules")); });
304
+ } catch (e2) { }
305
+ roots.push(join(home, "AppData", "Roaming", "npm", "node_modules"));
306
+
307
+ var seen = {}, installed = [];
308
+ function addPkg(dir, name, scope) {
309
+ if (seen["d::" + dir]) return; seen["d::" + dir] = true;
310
+ var pj = join(dir, "package.json");
311
+ if (!existsSync(pj)) return;
312
+ var j = {};
313
+ try { j = JSON.parse(readFileSync(pj, "utf8")); } catch (e) { return; }
314
+ var pname = j.name || name;
315
+ if (seen["p::" + pname]) return; seen["p::" + pname] = true;
316
+ var bin = "";
317
+ if (typeof j.bin === "string") bin = j.bin;
318
+ else if (j.bin && typeof j.bin === "object") { var ks = Object.keys(j.bin); if (ks.length) bin = j.bin[ks[0]]; }
319
+ installed.push({
320
+ name: pname,
321
+ version: j.version || "",
322
+ description: String(j.description || "").replace(/\s+/g, " ").slice(0, 180),
323
+ scope: scope,
324
+ dir: dir,
325
+ bin: bin,
326
+ configured: !!configuredHints[pname.toLowerCase()] || !!configuredNames[pname]
327
+ });
328
+ }
329
+ function scanDir(nmDir, scope) {
330
+ if (!existsSync(nmDir)) return;
331
+ var names = [];
332
+ try { names = readdirSync(nmDir); } catch (e) { return; }
333
+ for (var i = 0; i < names.length; i++) {
334
+ var nm = names[i];
335
+ if (!nm || nm.charAt(0) === ".") continue;
336
+ if (nm.charAt(0) === "@") {
337
+ try {
338
+ var subs = readdirSync(join(nmDir, nm));
339
+ for (var s = 0; s < subs.length; s++) {
340
+ if (/mcp/i.test(subs[s])) addPkg(join(nmDir, nm, subs[s]), nm + "/" + subs[s], scope);
341
+ }
342
+ } catch (e2) { }
343
+ continue;
344
+ }
345
+ if (/mcp/i.test(nm)) addPkg(join(nmDir, nm), nm, scope);
346
+ }
347
+ }
348
+ roots.forEach(function (r) { scanDir(r, r.indexOf("local-plugins") >= 0 ? "local-plugins" : "node_modules"); });
349
+
350
+ installed.sort(function (a, b) {
351
+ if (a.configured !== b.configured) return a.configured ? 1 : -1;
352
+ return a.name.localeCompare(b.name);
353
+ });
354
+ var notConf = installed.filter(function (x) { return !x.configured; });
355
+ return {
356
+ ok: true,
357
+ ts: Date.now(),
358
+ configured: servers.map(function (s) {
359
+ return { serverName: s.serverName, transport: s.transport, command: s.command, version: s.version,
360
+ disabled: s.disabled, phase: s.phase, toolCount: s.toolCount };
361
+ }),
362
+ installedTotal: installed.length,
363
+ installedNotConfigured: notConf.length,
364
+ installed: installed
365
+ };
366
+ }
367
+
368
+ // ── AI 修复:诊断异常 MCP(规则 + AI 增强建议),并应用修复动作 ──
369
+ export async function mcpAiDiagnose(ctx) {
370
+ var conf = collectMcp(ctx, true);
371
+ var servers = conf.servers || [];
372
+ var stats = getMcpStats();
373
+ var stBy = {};
374
+ (stats.servers || []).forEach(function (s) { stBy[s.server] = s; });
375
+ var issues = [];
376
+ servers.forEach(function (sv) {
377
+ var st = stBy[sv.serverName] || null;
378
+ var fail = st ? (st.fail || 0) : 0;
379
+ var phase = sv.phase || "";
380
+ var symptoms = [];
381
+ if (sv.disabled) symptoms.push("已停用");
382
+ if (phase === "failed" || phase === "error") symptoms.push("启动失败");
383
+ if (fail > 0) symptoms.push("调用失败 " + fail + " 次");
384
+ if (!symptoms.length) return;
385
+ var lastErr = "";
386
+ if (st && st.tools) { for (var i = 0; i < st.tools.length; i++) { if (st.tools[i].lastErr) { lastErr = st.tools[i].lastErr; break; } } }
387
+ issues.push({ id: sv.id, serverName: sv.serverName, phase: phase, disabled: !!sv.disabled,
388
+ fail: fail, lastErr: lastErr, symptoms: symptoms.join("、"), action: "", reason: "", source: "rule" });
389
+ });
390
+ if (!issues.length) return { ok: true, issues: [], note: "没有发现异常的 MCP" };
391
+ // 规则默认动作
392
+ issues.forEach(function (x) {
393
+ if (x.disabled) x.action = "enable";
394
+ else if (x.phase === "failed" || x.phase === "error") x.action = "reconnect";
395
+ else if (x.fail > 0) x.action = "reconnect";
396
+ else x.action = "keep";
397
+ x.reason = "(规则)" + x.symptoms;
398
+ });
399
+ // AI 增强(失败则保留规则默认)
400
+ try {
401
+ var sys = "你是 DSH 的 MCP 运维助手。根据每个 MCP 服务器的状态判断最合适的修复动作。" +
402
+ "可选动作:reconnect(重连) / enable(启用) / disable(暂时停用) / clean(清理残留) / keep(不动)。" +
403
+ '只输出 JSON:{"items":[{"serverName":"...","action":"reconnect","reason":"一句话中文原因"}]},不要解释。';
404
+ var u = "MCP 服务器状态:\n" + issues.map(function (x, i) {
405
+ return (i + 1) + ") " + x.serverName + " | 状态:" + x.phase + " | 停用:" + (x.disabled ? "是" : "否") +
406
+ " | 调用失败:" + x.fail + "次 | 最近错误:" + (x.lastErr || "无");
407
+ }).join("\n") + "\n请为每个给出 action 和 reason。";
408
+ var res = await askModel(ctx, null, sys, u, "skill-browser-mcp-fix", 1200, { reasoningEffort: "low", timeoutMs: 60000 });
409
+ var obj = null;
410
+ try { var t = String(res.text || ""); var a = t.indexOf("{"); var b = t.lastIndexOf("}"); if (a >= 0 && b > a) obj = JSON.parse(t.slice(a, b + 1)); } catch (e0) { }
411
+ if (obj && Array.isArray(obj.items)) {
412
+ var map = {}; obj.items.forEach(function (it) { if (it && it.serverName) map[String(it.serverName)] = it; });
413
+ issues.forEach(function (x) {
414
+ var it = map[x.serverName];
415
+ if (it && it.action && /^(reconnect|enable|disable|clean|keep)$/.test(it.action)) {
416
+ x.action = it.action; x.reason = "(AI)" + (it.reason || ""); x.source = "ai";
417
+ }
418
+ });
419
+ }
420
+ } catch (e) { /* 保留规则默认 */ }
421
+ return { ok: true, issues: issues, note: "" };
422
+ }
423
+
424
+ export async function mcpAiFixApply(ctx, actions) {
425
+ var results = [];
426
+ var conf = collectMcp(ctx, true);
427
+ var byName = {};
428
+ (conf.servers || []).forEach(function (s) { byName[s.serverName] = s; });
429
+ for (var i = 0; i < (actions || []).length; i++) {
430
+ var a = actions[i] || {};
431
+ var name = String(a.serverName || ""), act = String(a.action || "");
432
+ var r = { serverName: name, action: act, ok: false, error: "" };
433
+ try {
434
+ var sv = byName[name];
435
+ if (!sv) { r.error = "未找到该 MCP"; results.push(r); continue; }
436
+ if (act === "reconnect") { var rr = await reconnectMcp(ctx, sv.id); r.ok = !!(rr && rr.ok !== false); r.error = (rr && rr.error) || ""; }
437
+ else if (act === "enable") { var e1 = setMcpEnabled(ctx, sv.id, true); r.ok = !!(e1 && e1.ok !== false); r.error = (e1 && e1.error) || ""; }
438
+ else if (act === "disable") { var e2 = setMcpEnabled(ctx, sv.id, false); r.ok = !!(e2 && e2.ok !== false); r.error = (e2 && e2.error) || ""; }
439
+ else if (act === "clean") { var c1 = cleanMcpResidue(ctx); r.ok = !!(c1 && c1.ok !== false); r.error = (c1 && c1.error) || ""; }
440
+ else { r.ok = true; r.error = "已跳过"; }
441
+ } catch (e) { r.error = String((e && e.message) || e); }
442
+ results.push(r);
443
+ }
444
+ return { ok: true, results: results };
445
+ }
446
+
447
+ // ─────────────────────── 启停(受管覆盖块) ───────────────────────
448
+
449
+ function stripManagedBlock(text) {
450
+ var b = text.indexOf(MARK_BEGIN);
451
+ if (b < 0) return text;
452
+ var e = text.indexOf(MARK_END, b);
453
+ if (e < 0) return text; // 标记损坏:宁可不改
454
+ e += MARK_END.length;
455
+ while (e < text.length && (text[e] === "\r" || text[e] === "\n")) e++;
456
+ return text.slice(0, b) + text.slice(e);
457
+ }
458
+
459
+ function parseManagedBlock(text) {
460
+ var b = text.indexOf(MARK_BEGIN);
461
+ if (b < 0) return [];
462
+ var e = text.indexOf(MARK_END, b);
463
+ if (e < 0) return [];
464
+ var body = text.slice(b + MARK_BEGIN.length, e);
465
+ var lines = body.split(/\r?\n/);
466
+ var ids = [];
467
+ for (var i = 0; i < lines.length; i++) {
468
+ var m = lines[i].match(/^\s*-\s*id:\s*(.+?)\s*$/);
469
+ if (m) ids.push(m[1].replace(/^['"]|['"]$/g, ""));
470
+ }
471
+ return ids;
472
+ }
473
+
474
+ function backupFile(file) {
475
+ try {
476
+ mkdirSync(backupDir(), { recursive: true });
477
+ var name = basename(file) + ".mcp-" + new Date().toISOString().replace(/[:.]/g, "-");
478
+ copyFileSync(file, join(backupDir(), name));
479
+ // 只保留最近 20 份
480
+ var all = readdirSync(backupDir()).filter(function (n) { return n.indexOf(basename(file) + ".mcp-") === 0; }).sort();
481
+ while (all.length > 20) {
482
+ try { unlinkSync(join(backupDir(), all.shift())); } catch (e) { }
483
+ }
484
+ } catch (e) { /* 备份失败不阻断,但下面会记录 */ }
485
+ }
486
+
487
+ function writeManagedBlock(file, disabledIds) {
488
+ var text = readFileSync(file, "utf8");
489
+ var eol = text.indexOf("\r\n") >= 0 ? "\r\n" : "\n";
490
+ var stripped = stripManagedBlock(text);
491
+ var out = stripped;
492
+ if (disabledIds && disabledIds.length) {
493
+ var block = [MARK_BEGIN];
494
+ disabledIds.slice().sort().forEach(function (id) {
495
+ block.push("- id: " + id);
496
+ block.push(" disabled: true");
497
+ });
498
+ block.push(MARK_END);
499
+ if (out.length && !/[\r\n]$/.test(out)) out += eol;
500
+ out += block.join(eol) + eol;
501
+ }
502
+ if (out === text) return { changed: false, text: text };
503
+ backupFile(file);
504
+ writeFileSync(file, out, "utf8");
505
+ return { changed: true, text: out };
506
+ }
507
+
508
+ export function setMcpEnabled(ctx, id, enabled) {
509
+ var data = collectMcp(ctx, true);
510
+ var target = null;
511
+ for (var i = 0; i < data.servers.length; i++) if (data.servers[i].id === id) target = data.servers[i];
512
+ if (!target) return { ok: false, error: "找不到 MCP 条目: " + id };
513
+ var file = target.patchFile;
514
+ if (!file || !existsSync(file) || !safeContains(file, id)) file = fallbackPatchFile(id);
515
+ if (!file || !existsSync(file)) return { ok: false, error: "无法定位该 MCP 所在的 patch 文件(可能来自内置层,无法用文件覆盖)" };
516
+
517
+ var raw = readFileSync(file, "utf8");
518
+ var ids = parseManagedBlock(raw);
519
+ var set = {};
520
+ ids.forEach(function (x) { set[x] = true; });
521
+ if (enabled) delete set[id]; else set[id] = true;
522
+ var nextIds = Object.keys(set);
523
+
524
+ var res = writeManagedBlock(file, nextIds);
525
+ return {
526
+ ok: true,
527
+ changed: res.changed,
528
+ file: file,
529
+ disabledIds: nextIds,
530
+ note: res.changed
531
+ ? "配置已写入,DSH 用户补丁层会热重载(若 1~2 秒内状态未变,请彻底重启 DSH)"
532
+ : "配置无变化"
533
+ };
534
+ }
535
+
536
+ // 重连:纯内存重启该条目,不动文件
537
+ export async function reconnectMcp(ctx, id) {
538
+ var entry = null;
539
+ try {
540
+ for (var row of ctx.loader.entries()) {
541
+ if (row && row.options && String(row.options.id) === id && row.options.name === MCP_PKG) { entry = row; break; }
542
+ }
543
+ } catch (e) { return { ok: false, error: "loader 不可用: " + (e && e.message) }; }
544
+ if (!entry) return { ok: false, error: "找不到运行中的 MCP 条目: " + id };
545
+ if (entry.disabled) return { ok: false, error: "该 MCP 已停用,请先启用再重连" };
546
+ try {
547
+ var fiber = entry.fiber;
548
+ if (fiber && typeof fiber.update === "function") {
549
+ await fiber.update(entry.options.config, true);
550
+ return { ok: true, note: "已触发该条目的内存重启" };
551
+ }
552
+ return { ok: false, error: "该条目当前没有可重启的 fiber(可能尚未加载)" };
553
+ } catch (e) {
554
+ return { ok: false, error: String((e && e.message) || e) };
555
+ }
556
+ }
557
+
558
+ // ─────────────────────── 调用统计(jsonl) ───────────────────────
559
+
560
+ var STATS_MAX_BYTES = 3 * 1024 * 1024;
561
+
562
+ // v2.0.4 性能:内存缓冲 + 批量 flush(原来是每条工具结果都同步 appendFileSync
563
+ // + statSync,高频 MCP 调用会阻塞宿主事件循环,导致界面一卡一卡)。
564
+ var _mcpQueue = [];
565
+ var _mcpFlushTimer = null;
566
+ var MCP_FLUSH_BATCH = 20;
567
+ var MCP_FLUSH_DELAY_MS = 3000;
568
+
569
+ function flushMcpStats() {
570
+ if (_mcpFlushTimer) { try { clearTimeout(_mcpFlushTimer); } catch (e) { } _mcpFlushTimer = null; }
571
+ if (!_mcpQueue.length) return;
572
+ var batch = _mcpQueue;
573
+ _mcpQueue = [];
574
+ try {
575
+ mkdirSync(dataDir(), { recursive: true });
576
+ appendFileSync(mcpStatsFile(), batch.map(function (r) { return JSON.stringify(r); }).join("\n") + "\n", "utf8");
577
+ try {
578
+ var st = statSync(mcpStatsFile());
579
+ if (st.size > STATS_MAX_BYTES) compactStats();
580
+ } catch (e2) { /* ignore */ }
581
+ } catch (e) {
582
+ // 写盘失败:回塞(限量,绝不无限膨胀)
583
+ _mcpQueue = batch.concat(_mcpQueue).slice(-500);
584
+ }
585
+ }
586
+
587
+ export function recordMcpCall(server, tool, ok, err, durMs) {
588
+ try {
589
+ var rec = { ts: Date.now(), server: server, tool: tool, ok: !!ok, dur: durMs || 0 };
590
+ if (!ok && err) rec.err = String(err).slice(0, 200);
591
+ _mcpQueue.push(rec);
592
+ if (_mcpQueue.length > 2000) _mcpQueue.splice(0, _mcpQueue.length - 2000);
593
+ if (_mcpQueue.length >= MCP_FLUSH_BATCH) {
594
+ flushMcpStats();
595
+ } else if (!_mcpFlushTimer) {
596
+ _mcpFlushTimer = setTimeout(function () { _mcpFlushTimer = null; flushMcpStats(); }, MCP_FLUSH_DELAY_MS);
597
+ }
598
+ } catch (e) { /* 统计失败绝不影响工具调用 */ }
599
+ }
600
+ export { flushMcpStats };
601
+
602
+ function readStatsLines() {
603
+ var file = mcpStatsFile();
604
+ if (!existsSync(file)) return [];
605
+ var out = [];
606
+ try {
607
+ var txt = readFileSync(file, "utf8");
608
+ var lines = txt.split("\n");
609
+ for (var i = 0; i < lines.length; i++) {
610
+ var s = lines[i].trim();
611
+ if (!s) continue;
612
+ try { out.push(JSON.parse(s)); } catch (e) { /* 跳过坏行 */ }
613
+ }
614
+ } catch (e) { /* ignore */ }
615
+ return out;
616
+ }
617
+
618
+ function compactStats() {
619
+ try {
620
+ var rows = readStatsLines();
621
+ var keep = rows.slice(-3000);
622
+ writeFileSync(mcpStatsFile(), keep.map(function (r) { return JSON.stringify(r); }).join("\n") + "\n", "utf8");
623
+ } catch (e) { /* ignore */ }
624
+ }
625
+
626
+ export function getMcpStats(opts) {
627
+ opts = opts || {};
628
+ flushMcpStats(); // 先落盘缓冲,保证读到最新
629
+ var since = opts.since ? Number(opts.since) : 0;
630
+ var rows = readStatsLines();
631
+ var byServer = {};
632
+ var total = { calls: 0, ok: 0, fail: 0 };
633
+ var now = Date.now();
634
+ var series = {}; // 最近 24h 按小时
635
+ for (var i = 0; i < rows.length; i++) {
636
+ var r = rows[i];
637
+ if (!r || typeof r.ts !== "number") continue;
638
+ if (since && r.ts < since) continue;
639
+ var key = r.server || "(unknown)";
640
+ var b = byServer[key] || (byServer[key] = { server: key, calls: 0, ok: 0, fail: 0, lastTs: 0, tools: {} });
641
+ b.calls++; total.calls++;
642
+ if (r.ok) { b.ok++; total.ok++; } else { b.fail++; total.fail++; }
643
+ if (r.ts > b.lastTs) b.lastTs = r.ts;
644
+ var tk = r.tool || "(unknown)";
645
+ var tob = b.tools[tk] || (b.tools[tk] = { tool: tk, calls: 0, ok: 0, fail: 0, lastTs: 0, lastErr: "", durSum: 0 });
646
+ tob.calls++;
647
+ if (r.ok) tob.ok++; else { tob.fail++; if (r.err) tob.lastErr = String(r.err).slice(0, 160); }
648
+ if (r.ts > tob.lastTs) tob.lastTs = r.ts;
649
+ tob.durSum += (r.dur || 0);
650
+ if (now - r.ts < 24 * 3600 * 1000) {
651
+ var h = new Date(r.ts).getHours();
652
+ series[h] = (series[h] || 0) + 1;
653
+ }
654
+ }
655
+ var servers = Object.keys(byServer).map(function (k) {
656
+ var b = byServer[k];
657
+ var toolArr = Object.keys(b.tools).map(function (t) {
658
+ var x = b.tools[t];
659
+ return { tool: x.tool, calls: x.calls, ok: x.ok, fail: x.fail, lastTs: x.lastTs, lastErr: x.lastErr, avgDur: x.calls ? Math.round(x.durSum / x.calls) : 0 };
660
+ }).sort(function (a, c) { return c.calls - a.calls; });
661
+ var top = toolArr.slice(0, 10).map(function (x) { return { tool: x.tool, calls: x.calls }; });
662
+ return { server: b.server, calls: b.calls, ok: b.ok, fail: b.fail, lastTs: b.lastTs, topTools: top, tools: toolArr };
663
+ }).sort(function (a, b) { return b.calls - a.calls; });
664
+ var hourly = [];
665
+ for (var hh = 0; hh < 24; hh++) hourly.push(series[hh] || 0);
666
+ return { ok: true, ts: now, total: total, servers: servers, hourly: hourly, file: mcpStatsFile() };
667
+ }
668
+
669
+ export function clearMcpStats() {
670
+ try {
671
+ _mcpQueue = [];
672
+ if (_mcpFlushTimer) { try { clearTimeout(_mcpFlushTimer); } catch (e) { } _mcpFlushTimer = null; }
673
+ mkdirSync(dataDir(), { recursive: true });
674
+ writeFileSync(mcpStatsFile(), "", "utf8");
675
+ return { ok: true, note: "调用统计已清空" };
676
+ } catch (e) { return { ok: false, error: String((e && e.message) || e) }; }
677
+ }
678
+
679
+ // ─────────────────────── 导出 ───────────────────────
680
+
681
+ export function exportMcp(ctx, opts) {
682
+ opts = opts || {};
683
+ var mask = !!opts.mask;
684
+ var data = collectMcp(ctx, true);
685
+ // collectMcp 的 env/headers 是打码值(UI 展示用);导出需要原始值,
686
+ // 这里直接回读 loader 里每个条目的 config。
687
+ var rawById = {};
688
+ try {
689
+ for (var row of ctx.loader.entries()) {
690
+ if (row && row.options && row.options.name === MCP_PKG) rawById[String(row.options.id)] = row.options.config || {};
691
+ }
692
+ } catch (e) { /* ignore */ }
693
+ var mcpServers = {};
694
+ data.servers.forEach(function (s) {
695
+ var cfg = rawById[s.id] || {};
696
+ var env = (cfg.env && typeof cfg.env === "object") ? cfg.env : null;
697
+ var headers = (cfg.headers && typeof cfg.headers === "object") ? cfg.headers : null;
698
+ var o = {};
699
+ if (s.transport === "stdio") {
700
+ o.command = s.command;
701
+ if (s.args && s.args.length) o.args = s.args.slice();
702
+ if (s.cwd) o.cwd = s.cwd;
703
+ } else {
704
+ o.url = s.url;
705
+ }
706
+ if (env) {
707
+ o.env = {};
708
+ Object.keys(env).forEach(function (k) {
709
+ o.env[k] = mask && SENSITIVE_RE.test(k) ? maskValue(String(env[k])) : String(env[k]);
710
+ });
711
+ }
712
+ if (headers) {
713
+ o.headers = {};
714
+ Object.keys(headers).forEach(function (k) {
715
+ o.headers[k] = mask && SENSITIVE_RE.test(k) ? maskValue(String(headers[k])) : String(headers[k]);
716
+ });
717
+ }
718
+ mcpServers[s.serverName] = o;
719
+ });
720
+ return { ok: true, mcpServers: mcpServers };
721
+ }
722
+
723
+ // ─────────────────── v2.0.5 MCP 残留检测与清理 ───────────────────
724
+ // 场景:某 MCP 的包被卸载后,profile 的 cordis.patch.yml 里仍留着指向它的
725
+ // 配置块(第三方包自写的 `# >>> pkg: ... >>>` 机器块,或本插件的受管覆盖块)。
726
+ // 这些块会让 HMR 每次报 `patch: entry "X" not found`。这里提供检测 + 清理:
727
+ // 只删「整块内所有 id 都已不在运行树、且对应包已不在 node_modules」的块,
728
+ // 避免误删仍然有效的配置。
729
+ function _normId(id) { return String(id || "").replace(/^include:/, ""); }
730
+
731
+ function _profilePatchFiles() {
732
+ var files = [];
733
+ try {
734
+ var roots = [];
735
+ if (process.env.DSH_HOME) roots.push(join(process.env.DSH_HOME, "profiles"));
736
+ else roots.push(join(homedir(), ".dsh", "profiles"));
737
+ var seen = {};
738
+ roots.forEach(function (base) {
739
+ if (!existsSync(base)) return;
740
+ readdirSync(base).forEach(function (name) {
741
+ var f = join(base, name, "cordis.patch.yml");
742
+ if (!existsSync(f) || seen[f]) return;
743
+ seen[f] = true;
744
+ try { if (readFileSync(f, "utf8").indexOf(MCP_PKG) >= 0) files.push(f); } catch (e) { }
745
+ });
746
+ });
747
+ } catch (e) { }
748
+ return files;
749
+ }
750
+
751
+ function _liveIds(ctx) {
752
+ var live = {};
753
+ try {
754
+ for (var row of ctx.loader.entries()) if (row && row.options && row.options.id) live[_normId(row.options.id)] = true;
755
+ } catch (e) { }
756
+ return live;
757
+ }
758
+
759
+ function _pkgInstalled(name) {
760
+ if (!name) return false;
761
+ try {
762
+ var roots = [];
763
+ if (process.env.DSH_HOME) roots.push(join(process.env.DSH_HOME, "profiles"));
764
+ else roots.push(join(homedir(), ".dsh", "profiles"));
765
+ for (var r = 0; r < roots.length; r++) {
766
+ var base = roots[r];
767
+ if (!existsSync(base)) continue;
768
+ var names = readdirSync(base);
769
+ for (var i = 0; i < names.length; i++) if (existsSync(join(base, names[i], "node_modules", name))) return true;
770
+ }
771
+ if (existsSync(join(process.env.DSH_HOME || join(homedir(), ".dsh"), "local-plugins", name))) return true;
772
+ } catch (e) { }
773
+ return false;
774
+ }
775
+
776
+ function _lineIds(lines, start, end) {
777
+ var ids = [];
778
+ for (var i = start; i <= end; i++) {
779
+ var m = /^\s*-\s*id:\s*([^\s#]+)/.exec(lines[i] || "");
780
+ if (m) ids.push(m[1].replace(/^['"]|['"]$/g, ""));
781
+ }
782
+ return ids;
783
+ }
784
+
785
+ function _scanResidueFile(file, live) {
786
+ var out = [];
787
+ var txt;
788
+ try { txt = readFileSync(file, "utf8"); } catch (e) { return out; }
789
+ var lines = txt.split(/\r?\n/);
790
+ var allGoneOf = function (ids) { return ids.length > 0 && ids.every(function (id) { return !live[_normId(id)]; }); };
791
+ // 1) 本插件受管块
792
+ var i = 0;
793
+ while (i < lines.length) {
794
+ if (lines[i].indexOf(MARK_BEGIN) >= 0) {
795
+ var e = -1;
796
+ for (var j = i + 1; j < lines.length; j++) if (lines[j].indexOf(MARK_END) >= 0) { e = j; break; }
797
+ if (e < 0) break;
798
+ var ids = _lineIds(lines, i, e);
799
+ if (allGoneOf(ids)) out.push({ file: file, kind: "managed", start: i, end: e, ids: ids, reason: "本插件的受管覆盖块指向的条目已不存在" });
800
+ i = e + 1; continue;
801
+ }
802
+ i++;
803
+ }
804
+ // 2) 第三方机器块 # >>> name: ... >>> ... # <<< name <<<
805
+ var stack = [];
806
+ for (var k = 0; k < lines.length; k++) {
807
+ var mb = /^#\s*>>>\s*([^:>]+?)\s*:/.exec(lines[k]);
808
+ if (mb) { stack.push({ name: mb[1].trim(), start: k }); continue; }
809
+ var me = /^#\s*<<<\s*(.+?)\s*<<</.exec(lines[k]);
810
+ if (me && stack.length) {
811
+ var blk = stack.pop();
812
+ var ids2 = _lineIds(lines, blk.start, k);
813
+ if (allGoneOf(ids2) && !_pkgInstalled(blk.name)) {
814
+ out.push({ file: file, kind: "pkg", start: blk.start, end: k, ids: ids2, pkg: blk.name, reason: "包 " + blk.name + " 已不在 node_modules,配置块指向的条目不存在" });
815
+ }
816
+ }
817
+ }
818
+ return out;
819
+ }
820
+
821
+ export function detectMcpResidue(ctx) {
822
+ var live = _liveIds(ctx);
823
+ var residue = [];
824
+ _profilePatchFiles().forEach(function (f) { residue = residue.concat(_scanResidueFile(f, live)); });
825
+ return { ok: true, count: residue.length, residue: residue };
826
+ }
827
+
828
+ export function cleanMcpResidue(ctx) {
829
+ var det = detectMcpResidue(ctx);
830
+ if (!det.count) return { ok: true, cleaned: 0, note: "没有检测到残留,配置是干净的" };
831
+ var byFile = {};
832
+ det.residue.forEach(function (r) { (byFile[r.file] = byFile[r.file] || []).push(r); });
833
+ var cleaned = 0, details = [];
834
+ Object.keys(byFile).forEach(function (file) {
835
+ try {
836
+ var txt = readFileSync(file, "utf8");
837
+ var eol = txt.indexOf("\r\n") >= 0 ? "\r\n" : "\n";
838
+ var lines = txt.split(/\r?\n/);
839
+ backupFile(file);
840
+ var ranges = byFile[file].slice().sort(function (a, b) { return b.start - a.start; });
841
+ ranges.forEach(function (r) {
842
+ lines.splice(r.start, r.end - r.start + 1);
843
+ cleaned++;
844
+ details.push({ kind: r.kind, ids: r.ids, pkg: r.pkg || "" });
845
+ });
846
+ writeFileSync(file, lines.join(eol), "utf8");
847
+ _mcpListCache = { ts: 0, data: null }; // 配置变了,失效列表缓存
848
+ } catch (e) { details.push({ file: file, error: String((e && e.message) || e) }); }
849
+ });
850
+ return { ok: true, cleaned: cleaned, details: details };
851
+ }
852
+
853
+ export var MCP_META = { pkg: MCP_PKG, markBegin: MARK_BEGIN, markEnd: MARK_END };