dsh-vscode-mode 0.1.14 → 0.1.16

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
@@ -84,25 +84,35 @@ npm pack # 产物 tgz(含 lib/assets/src/cordis.patch.yml)
84
84
 
85
85
  ```
86
86
  src/
87
- ├── index.ts Host 入口:name/inject/apply(薄装配)
87
+ ├── index.ts Host 入口:name/inject/apply(薄装配,含兼容性报告启动日志)
88
88
  ├── shared/ ★ 双面契约(禁 node/react):types.ts(记录/归档/摘要)+ rpc.ts(11 个方法类型化)
89
+ │ + compat.ts(兼容性报告形状)+ mcp.ts(MCP 契约)
90
+ ├── compat.ts ★ Host 兼容层:身份常量、外部插件探测、路由/重复装配护栏、依赖守卫、兼容性报告
89
91
  ├── model.ts Host 纯域逻辑(可单测):normalize/markDecision/resolved/summary/reconstruct/批次/归档
90
92
  ├── store.ts Host 存储层:sidecar 读写合并 + 归档持久化 + stale 检测
91
93
  ├── workspace.ts Host 工作区文件扫描(TTL 缓存)+ 快速打开搜索
92
94
  ├── revert.ts Host 回滚/删除(fs + subprocess,fs.contains 边界校验)
93
95
  ├── registry.ts Host 每工作区记录桶注册表
94
- ├── rpc.ts Host RPC 分发表(类型化 handler 表替代巨型 switch)
95
- ├── routes.ts Host webServer 路由(/edrv/rpc、/edrv/assets/*、/edrv/vendor/*)
96
+ ├── rpc.ts Host RPC 分发表(类型化 handler 表替代巨型 switch,含 compat)
97
+ ├── routes.ts Host webServer 路由(/edrv/rpc、/edrv/assets/*、/edrv/vendor/*,带冲突护栏)
96
98
  └── client/
97
99
  ├── index.ts Client 入口:slot 注册(inject=['slots','timer'])
100
+ ├── compat.ts ★ Client 兼容层:设置桥三级降级(webUiSettings→settingsScope)、slot 安全注册、openPath 链式补丁、外部插件常量
98
101
  ├── rpc.ts Client 类型化 fetch 包装 + 诊断日志
99
102
  ├── events.ts 窗口事件助手(edrv:refresh/open-editor/show-launcher)
100
103
  ├── state/ records.ts(摘要/计数/空差异)+ regions.ts(差异区域/行裁剪)纯函数
101
104
  ├── monaco/ loader.ts(AMD 加载/语言映射)+ diffRender.ts(差异自绘渲染器)
102
105
  ├── styles/editor.css 编辑区样式(tsdown CSS-inline 注入)
103
106
  └── ui/ EditorView(编排)/ QuickOpen / DiffBox / DiffBarEmpty / DiffLauncher / DiffBadge
107
+ / McpSettings(含「兼容性」子 Tab)
104
108
  ```
105
109
 
110
+ **兼容层(`src/compat.ts` + `src/client/compat.ts`)**:集中处理与其他插件 / DSH 版本的适配——
111
+ 运行时探测 `@deepseek-ai/dsh-mcp-client`、设置桥(`webUiSettings` → `settingsScope`)等外部依赖,
112
+ 护栏检测 `/edrv` 路由前缀冲突与本插件重复装配(duplicate loader entry),
113
+ `@deepseek-ai/dsh-settings` / `schemastery` 以动态加载引入(缺失时插件仍可加载,设置持久化降级)。
114
+ 「VSCodeMode」设置页 →「兼容性」子 Tab 或 RPC `edrv.compat` 可查看完整报告。
115
+
106
116
  **扩展缝(VSCode 化后续迭代)**:新能力 = `shared/rpc.ts` 加方法 + `src/rpc.ts` 加 handler +
107
117
  `client/ui/` 加组件,其余模块零改动;`monaco/*` 是可复用的编辑器服务(资源树/对比/诊断面板共用)。
108
118
 
package/lib/client.js CHANGED
@@ -1347,7 +1347,7 @@ window.__ModuleLoader__.load({
1347
1347
  if (!diffRendererRef.current) diffRendererRef.current = createDiffRenderer((sid, t) => dbg(sid, t));
1348
1348
  const currentRecords = react.default.useMemo(() => {
1349
1349
  const list = [];
1350
- for (const rec of Object.values(records)) if (rec.path === active) list.push(rec);
1350
+ for (const rec of Object.values(records)) if (rec.path === active && rec.archived !== true) list.push(rec);
1351
1351
  return list;
1352
1352
  }, [records, active]);
1353
1353
  const regions = react.default.useMemo(() => diffRegions(currentRecords, content).filter((r) => !r.superseded), [currentRecords, content]);
@@ -1386,7 +1386,10 @@ window.__ModuleLoader__.load({
1386
1386
  rpc("edrv.list", { sessionId }).then((res) => {
1387
1387
  if (!res || !res.ok || !Array.isArray(res.records)) return;
1388
1388
  const map = {};
1389
- for (const r of res.records) map[r.callId] = r;
1389
+ for (const r of res.records) {
1390
+ if (r.archived === true) continue;
1391
+ map[r.callId] = r;
1392
+ }
1390
1393
  setRecords(map);
1391
1394
  }).catch((e) => setError("list异常:" + String(e)));
1392
1395
  };
@@ -2197,7 +2200,7 @@ window.__ModuleLoader__.load({
2197
2200
  }
2198
2201
  //#endregion
2199
2202
  //#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/mcp.css.mjs
2200
- const css = "/* dsh-vscode-mode MCP 管理界面样式。\n 全部使用 DSH 真实主题变量(--dsw-alias-*),不再用不存在的 fg-* 变量。 */\n.vsm-mcp-page{display:flex;flex-direction:column;gap:18px;width:100%;max-width:920px;box-sizing:border-box;color:var(--dsw-alias-label-primary,#e6e6e6);padding-bottom:32px}\n\n/* 顶部:标题 + 副标题 + 添加按钮 */\n.vsm-mcp-header{display:flex;align-items:flex-start;justify-content:space-between;gap:20px}\n.vsm-mcp-header h2{margin:0 0 4px;font-size:20px;font-weight:600;line-height:1.35;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-header p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}\n.vsm-primary{border:0;border-radius:6px;padding:8px 16px;background:var(--dsw-alias-button-primary-fill,#6d55ff);color:var(--dsw-alias-button-contrast-fill,#fff);font-size:13px;font-weight:500;cursor:pointer;white-space:nowrap}\n.vsm-primary:hover{background:var(--dsw-alias-button-primary-hover,#5a46e6)}\n.vsm-primary:disabled{opacity:.55;cursor:default}\n\n/* 子 Tab:我的 MCP / MCP 市场 */\n.vsm-mcp-tabs{display:flex;gap:24px;border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}\n.vsm-mcp-tabs button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);padding:9px 2px 11px;cursor:pointer;font-size:14px;position:relative}\n.vsm-mcp-tabs button:hover{color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-mcp-tabs button.active{color:var(--dsw-alias-label-primary,#fff);font-weight:500}\n.vsm-mcp-tabs button.active::after{content:\"\";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--dsw-alias-brand-primary,#6d55ff);border-radius:2px}\n\n/* 服务卡片 */\n.vsm-mcp-card{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;padding:14px 16px;margin-bottom:10px;background:var(--dsw-alias-bg-layer-2,transparent);transition:border-color .15s}\n.vsm-mcp-card:hover{border-color:var(--dsw-alias-border-l3,#555)}\n.vsm-mcp-card-head{display:flex;justify-content:space-between;align-items:center;gap:12px}\n.vsm-mcp-title{display:flex;align-items:center;gap:9px;font-weight:600;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6);min-width:0}\n.vsm-mcp-source{font-size:11px;font-weight:400;padding:2px 7px;border-radius:4px;background:var(--dsw-alias-bg-mask-2,#ffffff22);color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}\n.vsm-mcp-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;background:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-mcp-dot.connected{background:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-dot.connecting{background:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-mcp-dot.disabled{background:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-mcp-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}\n.vsm-mcp-actions button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);cursor:pointer;font-size:15px;width:28px;height:28px;border-radius:6px;line-height:1}\n.vsm-mcp-actions button:hover{color:var(--dsw-alias-label-primary,#fff);background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-mcp-actions button.vsm-danger:hover{color:var(--dsw-alias-state-error-primary,#f07878);background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}\n.vsm-switch{font-size:14px !important}\n.vsm-switch.on{color:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:9px 0}\n.vsm-mcp-error{color:var(--dsw-alias-state-error-primary,#f07878);font-size:12px;margin:8px 0}\n.vsm-mcp-banner{padding:9px 12px;border:1px solid var(--dsw-alias-state-error-secondary,#d9534f55);border-radius:6px;background:var(--dsw-alias-state-error-secondary,#d9534f1a);margin-bottom:10px}\n\n/* 工具 chips */\n.vsm-mcp-tools{display:flex;flex-wrap:wrap;gap:6px}\n.vsm-mcp-chip{font:12px var(--ds-font-family-code,ui-monospace,monospace);padding:4px 8px;border-radius:5px;background:var(--dsw-alias-bg-mask-2,#ffffff14);color:var(--dsw-alias-label-secondary,#c8c8c8);border:1px solid var(--dsw-alias-border-l1,#333)}\n\n/* 空态 / 加载态 */\n.vsm-mcp-empty{text-align:center;padding:56px 20px;color:var(--dsw-alias-label-tertiary,#9aa5b1);border:1px dashed var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;font-size:13px}\n\n/* 项目搜索下拉框 */\n.vsm-project-picker{position:relative;width:100%;z-index:4}\n.vsm-project-picker-trigger{width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;background:var(--dsw-alias-bg-layer-1,transparent);color:var(--dsw-alias-label-primary,#e6e6e6);cursor:pointer;text-align:left}\n.vsm-project-picker-trigger:hover{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-label{display:flex;flex-direction:column;min-width:0;font-size:13px;font-weight:500}\n.vsm-project-picker-label small{margin-top:3px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-arrow{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:16px}\n.vsm-project-picker-menu{position:absolute;top:calc(100% + 6px);left:0;right:0;padding:8px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:9px;background:var(--dsw-alias-bg-overlay,#202020);box-shadow:0 12px 36px #0008}\n.vsm-project-picker-search{width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#2b2b2b);color:var(--dsw-alias-label-primary,#eee);outline:none}\n.vsm-project-picker-search:focus{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-options{max-height:260px;overflow:auto;margin-top:6px}\n.vsm-project-option{display:flex;width:100%;flex-direction:column;gap:3px;padding:8px 9px;border:0;border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;text-align:left}\n.vsm-project-option:hover,.vsm-project-option.highlight{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-project-option.selected{color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-option small{overflow:hidden;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-empty{padding:18px 8px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:center}\n\n/* 项目级 MCP 分组 */\n.vsm-project{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;margin-bottom:14px;overflow:hidden;background:var(--dsw-alias-bg-layer-1,transparent)}\n.vsm-project-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:var(--dsw-alias-bg-mask-1,#ffffff0a)}\n.vsm-project-title{display:flex;align-items:center;gap:10px;min-width:0}\n.vsm-project-icon{color:var(--dsw-alias-brand-primary,#6d55ff);font-size:15px;flex-shrink:0}\n.vsm-project-name{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6);display:block}\n.vsm-project-path{font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-family:var(--ds-font-family-code,ui-monospace,monospace);display:block;margin-top:2px;word-break:break-all}\n.vsm-primary.vsm-small{padding:5px 12px;font-size:12px}\n.vsm-project-empty{text-align:center;padding:26px 16px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px}\n.vsm-project .vsm-mcp-card{border:0;border-radius:0;margin:0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:transparent}\n.vsm-project .vsm-mcp-card:last-child{border-bottom:0}\n.vsm-project .vsm-mcp-banner{border-radius:0;margin:0}\n\n/* 新增 MCP 弹窗 */\n.vsm-mcp-modal{position:fixed;inset:0;background:var(--dsw-alias-bg-mask-drop,#000000aa);display:flex;align-items:center;justify-content:center;z-index:50}\n.vsm-mcp-dialog{width:min(560px,calc(100vw - 40px));max-height:88vh;overflow:auto;padding:22px 24px;border-radius:12px;background:var(--dsw-alias-bg-overlay,#202020);border:1px solid var(--dsw-alias-border-l2,#444);box-shadow:0 16px 60px #000c;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-dialog h3{margin:0 0 14px;font-size:16px;font-weight:600}\n.vsm-mcp-dialog label{display:flex;flex-direction:column;gap:6px;margin:12px 0;font-size:12px;color:var(--dsw-alias-label-secondary,#aaa)}\n.vsm-mcp-dialog input,.vsm-mcp-dialog select,.vsm-mcp-dialog textarea{font:inherit;color:var(--dsw-alias-label-primary,#eee);background:var(--dsw-alias-bg-layer-1,#2b2b2b);border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 10px}\n.vsm-mcp-dialog input:focus,.vsm-mcp-dialog select:focus,.vsm-mcp-dialog textarea:focus{outline:none;border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-mcp-dialog textarea{min-height:64px;resize:vertical;font-family:var(--ds-font-family-code,ui-monospace,monospace)}\n.vsm-mcp-dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}\n.vsm-mcp-dialog-actions button{padding:8px 14px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:13px}\n.vsm-mcp-dialog-actions button:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n";
2203
+ const css = "/* dsh-vscode-mode MCP 管理界面样式。\n 全部使用 DSH 真实主题变量(--dsw-alias-*),不再用不存在的 fg-* 变量。 */\n.vsm-mcp-page{display:flex;flex-direction:column;gap:18px;width:100%;max-width:920px;box-sizing:border-box;color:var(--dsw-alias-label-primary,#e6e6e6);padding-bottom:32px}\n\n/* 顶部:标题 + 副标题 + 添加按钮 */\n.vsm-mcp-header{display:flex;align-items:flex-start;justify-content:space-between;gap:20px}\n.vsm-mcp-header h2{margin:0 0 4px;font-size:20px;font-weight:600;line-height:1.35;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-header p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}\n.vsm-primary{border:0;border-radius:6px;padding:8px 16px;background:var(--dsw-alias-button-primary-fill,#6d55ff);color:var(--dsw-alias-button-contrast-fill,#fff);font-size:13px;font-weight:500;cursor:pointer;white-space:nowrap}\n.vsm-primary:hover{background:var(--dsw-alias-button-primary-hover,#5a46e6)}\n.vsm-primary:disabled{opacity:.55;cursor:default}\n\n/* 子 Tab:我的 MCP / MCP 市场 */\n.vsm-mcp-tabs{display:flex;gap:24px;border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}\n.vsm-mcp-tabs button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);padding:9px 2px 11px;cursor:pointer;font-size:14px;position:relative}\n.vsm-mcp-tabs button:hover{color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-mcp-tabs button.active{color:var(--dsw-alias-label-primary,#fff);font-weight:500}\n.vsm-mcp-tabs button.active::after{content:\"\";position:absolute;left:0;right:0;bottom:-1px;height:2px;background:var(--dsw-alias-brand-primary,#6d55ff);border-radius:2px}\n\n/* 通用设置 */\n.vsm-general-page{display:flex;flex-direction:column;gap:10px;max-width:720px}\n.vsm-general-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-general-page p{margin:0 0 8px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px}\n.vsm-general-row{display:flex;align-items:center;justify-content:space-between;gap:20px;padding:14px 0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);color:var(--dsw-alias-label-primary,#eee);font-size:14px}\n.vsm-general-row select{min-width:220px;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#2b2b2b);color:var(--dsw-alias-label-primary,#eee);font:inherit}\n.vsm-general-row small{color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n\n/* 服务卡片 */\n.vsm-mcp-card{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;padding:14px 16px;margin-bottom:10px;background:var(--dsw-alias-bg-layer-2,transparent);transition:border-color .15s}\n.vsm-mcp-card:hover{border-color:var(--dsw-alias-border-l3,#555)}\n.vsm-mcp-card-head{display:flex;justify-content:space-between;align-items:center;gap:12px}\n.vsm-mcp-title{display:flex;align-items:center;gap:9px;font-weight:600;font-size:14px;color:var(--dsw-alias-label-primary,#e6e6e6);min-width:0}\n.vsm-mcp-source{font-size:11px;font-weight:400;padding:2px 7px;border-radius:4px;background:var(--dsw-alias-bg-mask-2,#ffffff22);color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}\n.vsm-mcp-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0;background:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-mcp-dot.connected{background:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-dot.connecting{background:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-mcp-dot.disabled{background:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-mcp-actions{display:flex;align-items:center;gap:4px;flex-shrink:0}\n.vsm-mcp-actions button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);cursor:pointer;font-size:15px;width:28px;height:28px;border-radius:6px;line-height:1}\n.vsm-mcp-actions button:hover{color:var(--dsw-alias-label-primary,#fff);background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-mcp-actions button.vsm-danger:hover{color:var(--dsw-alias-state-error-primary,#f07878);background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}\n.vsm-switch{font-size:14px !important}\n.vsm-switch.on{color:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-mcp-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:9px 0}\n.vsm-mcp-error{color:var(--dsw-alias-state-error-primary,#f07878);font-size:12px;margin:8px 0}\n.vsm-mcp-banner{padding:9px 12px;border:1px solid var(--dsw-alias-state-error-secondary,#d9534f55);border-radius:6px;background:var(--dsw-alias-state-error-secondary,#d9534f1a);margin-bottom:10px}\n\n/* 工具 chips */\n.vsm-mcp-tools{display:flex;flex-wrap:wrap;gap:6px}\n.vsm-mcp-chip{font:12px var(--ds-font-family-code,ui-monospace,monospace);padding:4px 8px;border-radius:5px;background:var(--dsw-alias-bg-mask-2,#ffffff14);color:var(--dsw-alias-label-secondary,#c8c8c8);border:1px solid var(--dsw-alias-border-l1,#333)}\n\n/* 空态 / 加载态 */\n.vsm-mcp-empty{text-align:center;padding:56px 20px;color:var(--dsw-alias-label-tertiary,#9aa5b1);border:1px dashed var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;font-size:13px}\n\n/* 项目搜索下拉框 */\n.vsm-project-picker{position:relative;width:100%;z-index:4}\n.vsm-project-picker-trigger{width:100%;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;background:var(--dsw-alias-bg-layer-1,transparent);color:var(--dsw-alias-label-primary,#e6e6e6);cursor:pointer;text-align:left}\n.vsm-project-picker-trigger:hover{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-label{display:flex;flex-direction:column;min-width:0;font-size:13px;font-weight:500}\n.vsm-project-picker-label small{margin-top:3px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-arrow{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:16px}\n.vsm-project-picker-menu{position:absolute;top:calc(100% + 6px);left:0;right:0;padding:8px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:9px;background:var(--dsw-alias-bg-overlay,#202020);box-shadow:0 12px 36px #0008}\n.vsm-project-picker-search{width:100%;box-sizing:border-box;padding:8px 10px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#2b2b2b);color:var(--dsw-alias-label-primary,#eee);outline:none}\n.vsm-project-picker-search:focus{border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-picker-options{max-height:260px;overflow:auto;margin-top:6px}\n.vsm-project-option{display:flex;width:100%;flex-direction:column;gap:3px;padding:8px 9px;border:0;border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;text-align:left}\n.vsm-project-option:hover,.vsm-project-option.highlight{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-project-option.selected{color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-project-option small{overflow:hidden;color:var(--dsw-alias-label-tertiary,#9aa5b1);font:11px var(--ds-font-family-code,ui-monospace,monospace);text-overflow:ellipsis;white-space:nowrap}\n.vsm-project-picker-empty{padding:18px 8px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:center}\n\n/* 项目级 MCP 分组 */\n.vsm-project{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;margin-bottom:14px;overflow:hidden;background:var(--dsw-alias-bg-layer-1,transparent)}\n.vsm-project-head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:var(--dsw-alias-bg-mask-1,#ffffff0a)}\n.vsm-project-title{display:flex;align-items:center;gap:10px;min-width:0}\n.vsm-project-icon{color:var(--dsw-alias-brand-primary,#6d55ff);font-size:15px;flex-shrink:0}\n.vsm-project-name{font-size:14px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6);display:block}\n.vsm-project-path{font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-family:var(--ds-font-family-code,ui-monospace,monospace);display:block;margin-top:2px;word-break:break-all}\n.vsm-primary.vsm-small{padding:5px 12px;font-size:12px}\n.vsm-project-empty{text-align:center;padding:26px 16px;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px}\n.vsm-project .vsm-mcp-card{border:0;border-radius:0;margin:0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);background:transparent}\n.vsm-project .vsm-mcp-card:last-child{border-bottom:0}\n.vsm-project .vsm-mcp-banner{border-radius:0;margin:0}\n\n/* 新增 MCP 弹窗 */\n.vsm-mcp-modal{position:fixed;inset:0;background:var(--dsw-alias-bg-mask-drop,#000000aa);display:flex;align-items:center;justify-content:center;z-index:50}\n.vsm-mcp-dialog{width:min(560px,calc(100vw - 40px));max-height:88vh;overflow:auto;padding:22px 24px;border-radius:12px;background:var(--dsw-alias-bg-overlay,#202020);border:1px solid var(--dsw-alias-border-l2,#444);box-shadow:0 16px 60px #000c;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-mcp-dialog h3{margin:0 0 14px;font-size:16px;font-weight:600}\n.vsm-mcp-dialog label{display:flex;flex-direction:column;gap:6px;margin:12px 0;font-size:12px;color:var(--dsw-alias-label-secondary,#aaa)}\n.vsm-mcp-dialog input,.vsm-mcp-dialog select,.vsm-mcp-dialog textarea{font:inherit;color:var(--dsw-alias-label-primary,#eee);background:var(--dsw-alias-bg-layer-1,#2b2b2b);border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 10px}\n.vsm-mcp-dialog input:focus,.vsm-mcp-dialog select:focus,.vsm-mcp-dialog textarea:focus{outline:none;border-color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-mcp-dialog textarea{min-height:64px;resize:vertical;font-family:var(--ds-font-family-code,ui-monospace,monospace)}\n.vsm-mcp-dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:20px}\n.vsm-mcp-dialog-actions button{padding:8px 14px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:13px}\n.vsm-mcp-dialog-actions button:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n\n/* 兼容性小节 */\n.vsm-compat-h3{margin:16px 0 2px;font-size:14px;font-weight:600;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-compat-item{display:flex;align-items:baseline;justify-content:space-between;gap:16px;padding:10px 0;border-bottom:1px solid var(--dsw-alias-border-l1,#333);font-size:13px;color:var(--dsw-alias-label-primary,#eee)}\n.vsm-compat-item.ok .vsm-compat-name::before{content:\"● \";color:var(--dsw-alias-state-success-primary,#38bd75)}\n.vsm-compat-item.warn .vsm-compat-name::before{content:\"● \";color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-compat-name{font-weight:500;min-width:0}\n.vsm-compat-note{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;text-align:right;min-width:0}\n";
2201
2204
  const tagId = "dsh-vscode-mode/mcp.css";
2202
2205
  if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
2203
2206
  const tag = document.createElement("style");
@@ -2207,6 +2210,218 @@ window.__ModuleLoader__.load({
2207
2210
  document.head.appendChild(tag);
2208
2211
  }
2209
2212
  //#endregion
2213
+ //#region src/client/compat.ts
2214
+ /**
2215
+ * dsh-vscode-mode client — 兼容层:设置桥选择 / slot 安全注册 / openPath 链式补丁 / 外部插件常量。
2216
+ * 纯逻辑与薄 ctx 胶水,node 环境可单测(不依赖 DOM)。
2217
+ * 作者 ddj 2026年08月24号
2218
+ */
2219
+ /** 本插件包名(与 host compat.PLUGIN_NAME 对应)。 */
2220
+ const PLUGIN_NAME = "dsh-vscode-mode";
2221
+ /** 外部侧栏插件名(dsh-web-ui-all 家族的文件打开能力提供方)。 */
2222
+ const SIDEBAR_PLUGIN = "dsh-better-sidebar";
2223
+ /**
2224
+ * 设置桥三级优先:webUiSettings(linxin666 兼容桥)→ settingsScope(官方)→ 无。
2225
+ * 桥绑定抛错时降级到下一级,不让单个桥异常拖垮设置装配。
2226
+ * @author ddj 2026年08月24号
2227
+ * @param ctx 客户端服务上下文
2228
+ * @returns 激活的桥名称与绑定 scope
2229
+ */
2230
+ function pickSettingsBinder(ctx) {
2231
+ for (const service of ["webUiSettings", "settingsScope"]) try {
2232
+ const scope = ctx.get(service)?.bind?.({ namespace: PLUGIN_NAME });
2233
+ if (scope) return {
2234
+ service,
2235
+ scope
2236
+ };
2237
+ } catch {}
2238
+ return {
2239
+ service: "none",
2240
+ scope: void 0
2241
+ };
2242
+ }
2243
+ /**
2244
+ * 跨版本 slot 安全注册:封装 ctx.slots.inject 等待声明模式(新老 DSH 通用),
2245
+ * 服务缺失或注册抛错时降级为警告,不让单条 slot 拖垮装配。
2246
+ * 注意:方法必须挂在服务对象上调用(slots.register 是依赖 this.records 的类方法,
2247
+ * 解构后 this 丢失会静默失败),故禁止解构。
2248
+ * @author ddj 2026年08月24号
2249
+ * @param ctx 客户端上下文(slots 服务)
2250
+ * @param spec slot 描述
2251
+ * @param render 渲染函数
2252
+ */
2253
+ function registerSlotSafely(ctx, spec, render) {
2254
+ const slots = ctx?.slots;
2255
+ if (!slots || typeof slots.inject !== "function" || typeof slots.register !== "function") {
2256
+ console.warn("[dsh-vscode-mode] slots 服务不可用,跳过 slot " + spec.name);
2257
+ return;
2258
+ }
2259
+ try {
2260
+ slots.inject(spec.name, () => slots.register(spec, render));
2261
+ } catch (error) {
2262
+ console.warn("[dsh-vscode-mode] slot " + spec.name + " 注册失败(" + String(error) + "),已跳过");
2263
+ }
2264
+ }
2265
+ /**
2266
+ * 链式补丁对象方法:保存原实现、装入包装实现,dispose 恢复。
2267
+ * 恢复带归属校验(仅当当前实现仍是本包装时还原),多插件补丁按栈序互不踩踏。
2268
+ * @author ddj 2026年08月24号
2269
+ * @param owner 目标对象
2270
+ * @param key 方法名
2271
+ * @param wrapper 包装实现(original 为补丁前的实现)
2272
+ * @returns 恢复函数(幂等)
2273
+ */
2274
+ function patchMethod(owner, key, wrapper) {
2275
+ const original = owner[key];
2276
+ const patched = ((...args) => wrapper(original, ...args));
2277
+ owner[key] = patched;
2278
+ return () => {
2279
+ if (owner[key] === patched) owner[key] = original;
2280
+ };
2281
+ }
2282
+ //#endregion
2283
+ //#region src/client/fileOpeners.ts
2284
+ /**
2285
+ * dsh-vscode-mode client — 文件打开器注册表与自动选择。
2286
+ * @author ddj 2026年08月24号
2287
+ */
2288
+ const AUTO_OPEN_TOOL = "auto";
2289
+ const VSCODE_OPEN_TOOL = "dsh-vscode-mode";
2290
+ const SIDEBAR_OPEN_TOOL = SIDEBAR_PLUGIN;
2291
+ /**
2292
+ * 写入外部打开器并触发注册表通知。
2293
+ * @author ddj 2026年08月24号
2294
+ * @param entries 打开器存储
2295
+ * @param notify 变更通知
2296
+ * @param opener 打开器
2297
+ */
2298
+ function registryRegister(entries, notify, opener) {
2299
+ if (!opener.id || typeof opener.open !== "function") throw new TypeError("文件打开器必须提供 id 和 open");
2300
+ entries.set(opener.id, opener);
2301
+ notify();
2302
+ }
2303
+ /**
2304
+ * 创建生命周期独立的文件打开器注册表。
2305
+ * @author ddj 2026年08月24号
2306
+ * @returns 文件打开器注册表
2307
+ */
2308
+ function createFileOpenerRegistry() {
2309
+ const entries = /* @__PURE__ */ new Map();
2310
+ const listeners = /* @__PURE__ */ new Set();
2311
+ const notify = () => {
2312
+ for (const listener of listeners) listener();
2313
+ };
2314
+ const list = () => [...entries.values()].sort((a, b) => (b.priority ?? 0) - (a.priority ?? 0));
2315
+ return {
2316
+ register(opener) {
2317
+ registryRegister(entries, notify, opener);
2318
+ return () => {
2319
+ if (entries.get(opener.id) !== opener) return;
2320
+ entries.delete(opener.id);
2321
+ notify();
2322
+ };
2323
+ },
2324
+ registerFileOpener(opener) {
2325
+ registryRegister(entries, notify, opener);
2326
+ return () => {
2327
+ if (entries.get(opener.id) !== opener) return;
2328
+ entries.delete(opener.id);
2329
+ notify();
2330
+ };
2331
+ },
2332
+ list,
2333
+ subscribe(listener) {
2334
+ listeners.add(listener);
2335
+ return () => listeners.delete(listener);
2336
+ },
2337
+ get: (id) => entries.get(id)
2338
+ };
2339
+ }
2340
+ /**
2341
+ * 检查打开器当前是否可用。
2342
+ * @author ddj 2026年08月24号
2343
+ * @param opener 待检查打开器
2344
+ * @returns 是否可用
2345
+ */
2346
+ function isAvailable(opener) {
2347
+ try {
2348
+ return opener.isAvailable ? opener.isAvailable() : true;
2349
+ } catch {
2350
+ return false;
2351
+ }
2352
+ }
2353
+ /**
2354
+ * 返回当前可展示的文件打开器。
2355
+ * @author ddj 2026年08月24号
2356
+ * @param registry 文件打开器注册表
2357
+ * @returns 可用打开器列表
2358
+ */
2359
+ function availableOpeners(registry) {
2360
+ return registry.list().filter(isAvailable);
2361
+ }
2362
+ /**
2363
+ * 按设置选择打开器,不可用时回退自动优先级。
2364
+ * @author ddj 2026年08月24号
2365
+ * @param registry 文件打开器注册表
2366
+ * @param selected 用户选择的打开器 id
2367
+ * @returns 选中的打开器
2368
+ */
2369
+ function selectOpener(registry, selected) {
2370
+ if (selected !== "auto") {
2371
+ const requested = registry.get(selected);
2372
+ if (requested && isAvailable(requested)) return requested;
2373
+ }
2374
+ return availableOpeners(registry)[0];
2375
+ }
2376
+ /**
2377
+ * 结构化探测 dsh-better-sidebar 的文件打开能力。
2378
+ * @author ddj 2026年08月24号
2379
+ * @param ctx 客户端服务上下文
2380
+ * @returns 侧边栏打开器或 undefined
2381
+ */
2382
+ function scanSidebar(ctx) {
2383
+ try {
2384
+ const service = ctx.get("betterSidebar");
2385
+ if (!service || typeof service.openFile !== "function") return void 0;
2386
+ if (!(service.features?.includes("openFile") ?? true)) return void 0;
2387
+ if (!(typeof service.isTabEnabled !== "function" || service.isTabEnabled("editor"))) return void 0;
2388
+ return {
2389
+ id: SIDEBAR_OPEN_TOOL,
2390
+ label: "dsh-better-sidebar",
2391
+ description: "在 dsh-web-ui-all 侧边栏编辑器中打开",
2392
+ priority: 80,
2393
+ isAvailable: () => typeof service.openFile === "function" && (service.features?.includes("openFile") ?? true) && (typeof service.isTabEnabled !== "function" || service.isTabEnabled("editor")),
2394
+ open: (path, context) => {
2395
+ if (!context.sessionId) throw new Error("当前没有活动会话");
2396
+ service.openFile({
2397
+ sessionId: context.sessionId,
2398
+ cwd: context.cwd
2399
+ }, path, baseName(path));
2400
+ }
2401
+ };
2402
+ } catch {
2403
+ return;
2404
+ }
2405
+ }
2406
+ /**
2407
+ * 从路径提取文件名。
2408
+ * @author ddj 2026年08月24号
2409
+ * @param path 文件路径
2410
+ * @returns 文件名
2411
+ */
2412
+ function baseName(path) {
2413
+ const normalized = path.replace(/\\/g, "/");
2414
+ return normalized.slice(normalized.lastIndexOf("/") + 1) || path;
2415
+ }
2416
+ //#endregion
2417
+ //#region src/client/settingsContext.ts
2418
+ /**
2419
+ * dsh-vscode-mode client — 设置 scope 的跨组件上下文。
2420
+ * 绑定逻辑(webUiSettings 兼容桥 → 官方 settingsScope 降级)统一由 compat 层提供。
2421
+ * 作者 ddj 2026年08月24号
2422
+ */
2423
+ const SettingsContext = react.default.createContext(void 0);
2424
+ //#endregion
2210
2425
  //#region src/client/ui/McpSettings.ts
2211
2426
  /**
2212
2427
  * dsh-vscode-mode client — VSCodeMode 设置区内的 MCP 管理。
@@ -2359,14 +2574,91 @@ window.__ModuleLoader__.load({
2359
2574
  }));
2360
2575
  return react.default.createElement("section", { className: "vsm-project" }, head, body);
2361
2576
  }
2577
+ /** 通用设置:选择当前对话文件链接的打开器。 */
2578
+ function GeneralSettings({ registry }) {
2579
+ const [tool, setTool] = react.default.useState(AUTO_OPEN_TOOL);
2580
+ const [busy, setBusy] = react.default.useState(false);
2581
+ const [error, setError] = react.default.useState("");
2582
+ const settings = react.default.useContext(SettingsContext);
2583
+ const snapshot = settings?.getSnapshot?.();
2584
+ const loading = !snapshot || snapshot.status === "loading";
2585
+ const unavailable = snapshot?.status === "unavailable" || !settings;
2586
+ const notReady = snapshot?.status !== "ready";
2587
+ react.default.useEffect(() => {
2588
+ const onChange = () => {
2589
+ const next = settings?.getSnapshot?.().value?.fileOpenTool;
2590
+ if (typeof next === "string") setTool(next);
2591
+ };
2592
+ onChange();
2593
+ return settings?.subscribe?.(onChange);
2594
+ }, [settings]);
2595
+ const save = (value) => {
2596
+ setTool(value);
2597
+ setBusy(true);
2598
+ setError("");
2599
+ if (!settings?.set) {
2600
+ setError("设置服务不可用");
2601
+ setBusy(false);
2602
+ return;
2603
+ }
2604
+ settings.set("fileOpenTool", value).catch((e) => setError(String(e))).finally(() => setBusy(false));
2605
+ };
2606
+ const options = [{
2607
+ id: AUTO_OPEN_TOOL,
2608
+ label: "自动选择",
2609
+ description: "按当前已注册工具自动选择"
2610
+ }, ...availableOpeners(registry).map((item) => ({
2611
+ id: item.id,
2612
+ label: item.label,
2613
+ description: item.description
2614
+ }))];
2615
+ const currentOption = options.some((item) => item.id === tool) ? tool : AUTO_OPEN_TOOL;
2616
+ return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "通用"), react.default.createElement("p", null, "配置 DSH 对话中文件链接的打开工具。工具由当前运行时自动扫描。"), unavailable && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, "设置服务暂不可用,当前使用自动选择。"), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), react.default.createElement("label", { className: "vsm-general-row" }, react.default.createElement("span", null, "文件链接使用工具"), react.default.createElement("select", {
2617
+ value: currentOption,
2618
+ disabled: loading || unavailable || notReady || busy || snapshot?.writable === false,
2619
+ onChange: (event) => save(event.target.value)
2620
+ }, options.map((item) => react.default.createElement("option", {
2621
+ key: item.id,
2622
+ value: item.id
2623
+ }, item.label))), loading && react.default.createElement("small", null, "读取中…")));
2624
+ }
2625
+ /** 兼容性小节:host 报告(RPC compat)+ 客户端本地适配项。 */
2626
+ function CompatSection({ getSummary }) {
2627
+ const [report, setReport] = react.default.useState(null);
2628
+ const [error, setError] = react.default.useState("");
2629
+ react.default.useEffect(() => {
2630
+ let alive = true;
2631
+ rpc("compat", {}).then((result) => {
2632
+ if (!alive) return;
2633
+ if (!result.ok) setError(String(result.error));
2634
+ else setReport(result.report);
2635
+ }).catch((e) => {
2636
+ if (alive) setError(String(e));
2637
+ });
2638
+ return () => {
2639
+ alive = false;
2640
+ };
2641
+ }, []);
2642
+ const renderItems = (list) => (list ?? []).map((item) => react.default.createElement("div", {
2643
+ key: item.name,
2644
+ className: "vsm-compat-item " + (item.active ? "ok" : "warn")
2645
+ }, react.default.createElement("span", { className: "vsm-compat-name" }, item.name), react.default.createElement("span", { className: "vsm-compat-note" }, item.note ?? (item.active ? "正常" : "未生效"))));
2646
+ const warnings = report?.warnings ?? [];
2647
+ return react.default.createElement("section", { className: "vsm-general-page" }, react.default.createElement("h2", null, "兼容性"), react.default.createElement("p", null, "检测与其他插件 / DSH 版本的适配状态与已知问题。" + (report?.pluginVersion ? "(插件版本 " + report.pluginVersion + ")" : "")), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), react.default.createElement("h3", { className: "vsm-compat-h3" }, "外部插件与服务"), renderItems(report?.external), react.default.createElement("h3", { className: "vsm-compat-h3" }, "客户端适配"), renderItems(getSummary?.() ?? []), react.default.createElement("h3", { className: "vsm-compat-h3" }, "护栏"), renderItems(report?.guards), react.default.createElement("h3", { className: "vsm-compat-h3" }, "警告"), warnings.length ? warnings.map((w, index) => react.default.createElement("div", {
2648
+ key: index,
2649
+ className: "vsm-mcp-error vsm-mcp-banner"
2650
+ }, w)) : react.default.createElement("div", { className: "vsm-compat-item ok" }, react.default.createElement("span", { className: "vsm-compat-name" }, "未检测到兼容性问题"), react.default.createElement("span", { className: "vsm-compat-note" }, "")));
2651
+ }
2362
2652
  /** MCP 管理主面板。 */
2363
- function McpSettings() {
2653
+ function McpSettings(props) {
2654
+ const openerRegistry = props?.openerRegistry ?? { list: () => [] };
2655
+ const compatSummary = props?.compatSummary ?? (() => []);
2364
2656
  const [servers, setServers] = react.default.useState([]);
2365
2657
  const [projects, setProjects] = react.default.useState([]);
2366
2658
  const [loading, setLoading] = react.default.useState(true);
2367
2659
  const [busy, setBusy] = react.default.useState("");
2368
2660
  const [error, setError] = react.default.useState("");
2369
- const [tab, setTab] = react.default.useState("mine");
2661
+ const [tab, setTab] = react.default.useState("general");
2370
2662
  const [draft, setDraft] = react.default.useState(EMPTY);
2371
2663
  const [showForm, setShowForm] = react.default.useState(false);
2372
2664
  const [projectForm, setProjectForm] = react.default.useState(null);
@@ -2477,6 +2769,7 @@ window.__ModuleLoader__.load({
2477
2769
  }) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "请选择项目")) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有项目");
2478
2770
  let body;
2479
2771
  if (loading) body = react.default.createElement("div", { className: "vsm-mcp-empty" }, "正在读取 MCP 服务…");
2772
+ else if (tab === "general") body = react.default.createElement(GeneralSettings, { registry: openerRegistry });
2480
2773
  else if (tab === "mine") body = react.default.createElement("div", null, servers.length ? servers.map((s) => react.default.createElement(ServerCard, {
2481
2774
  key: s.id,
2482
2775
  server: s,
@@ -2485,6 +2778,7 @@ window.__ModuleLoader__.load({
2485
2778
  onRemove: removeGlobal
2486
2779
  })) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有配置全局 MCP"));
2487
2780
  else if (tab === "projects") body = projectBody;
2781
+ else if (tab === "compat") body = react.default.createElement(CompatSection, { getSummary: compatSummary });
2488
2782
  else body = marketBody;
2489
2783
  const form = showForm ? react.default.createElement(McpForm, {
2490
2784
  title: "添加全局 MCP",
@@ -2508,6 +2802,9 @@ window.__ModuleLoader__.load({
2508
2802
  setShowForm(true);
2509
2803
  }
2510
2804
  }, "+ 添加全局 MCP")), react.default.createElement("nav", { className: "vsm-mcp-tabs" }, react.default.createElement("button", {
2805
+ className: tab === "general" ? "active" : "",
2806
+ onClick: () => setTab("general")
2807
+ }, "通用"), react.default.createElement("button", {
2511
2808
  className: tab === "mine" ? "active" : "",
2512
2809
  onClick: () => setTab("mine")
2513
2810
  }, "我的 MCP"), react.default.createElement("button", {
@@ -2516,7 +2813,10 @@ window.__ModuleLoader__.load({
2516
2813
  }, "项目 MCP"), react.default.createElement("button", {
2517
2814
  className: tab === "market" ? "active" : "",
2518
2815
  onClick: () => setTab("market")
2519
- }, "MCP 市场")), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), body, form);
2816
+ }, "MCP 市场"), react.default.createElement("button", {
2817
+ className: tab === "compat" ? "active" : "",
2818
+ onClick: () => setTab("compat")
2819
+ }, "兼容性")), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), body, form);
2520
2820
  }
2521
2821
  /** MCP 新增表单(全局/项目共用)。 */
2522
2822
  function McpForm({ title, draft, busy, edit, save, close }) {
@@ -2542,6 +2842,56 @@ window.__ModuleLoader__.load({
2542
2842
  }, "保存并连接"))));
2543
2843
  }
2544
2844
  //#endregion
2845
+ //#region src/client/openPathRouter.ts
2846
+ /**
2847
+ * dsh-vscode-mode client — DSH 对话文件链接的统一打开路由。
2848
+ * openPath 补丁经 compat.patchMethod 安装(链式恢复,多插件补丁互不踩踏)。
2849
+ * 作者 ddj 2026年08月24号
2850
+ */
2851
+ /**
2852
+ * 安装可卸载的 workspaces.openPath 路由。
2853
+ * @author ddj 2026年08月24号
2854
+ * @param options 路由依赖
2855
+ * @returns 恢复原方法的 disposer
2856
+ */
2857
+ function installOpenPathRouter(options) {
2858
+ const open = async (original, path) => {
2859
+ const context = options.context();
2860
+ const opener = selectOpener(options.registry, options.selected());
2861
+ if (!opener || !isAvailable(opener)) return original.call(options.workspaces, path);
2862
+ try {
2863
+ await opener.open(path, context);
2864
+ } catch (error) {
2865
+ options.logger?.("文件打开器 " + opener.id + " 失败,回退系统打开: " + String(error));
2866
+ if (opener.id !== "system") await original.call(options.workspaces, path);
2867
+ else throw error;
2868
+ }
2869
+ };
2870
+ return patchMethod(options.workspaces, "openPath", open);
2871
+ }
2872
+ /**
2873
+ * 创建 VSCodeMode 内置文件打开器。
2874
+ * @author ddj 2026年08月24号
2875
+ * @returns VSCodeMode 打开器
2876
+ */
2877
+ function vscodeOpener() {
2878
+ return {
2879
+ id: VSCODE_OPEN_TOOL,
2880
+ label: "VSCodeMode",
2881
+ priority: 100,
2882
+ open: (path) => openEditorView(path)
2883
+ };
2884
+ }
2885
+ /**
2886
+ * 规范化设置中的打开器 id。
2887
+ * @author ddj 2026年08月24号
2888
+ * @param value 原始设置值
2889
+ * @returns 可路由的打开器 id
2890
+ */
2891
+ function autoValue(value) {
2892
+ return typeof value === "string" && value.trim() ? value.trim() : AUTO_OPEN_TOOL;
2893
+ }
2894
+ //#endregion
2545
2895
  //#region src/client/index.ts
2546
2896
  /**
2547
2897
  * dsh-vscode-mode client — 浏览器半入口:slot 注册 + 装配。
@@ -2561,7 +2911,11 @@ window.__ModuleLoader__.load({
2561
2911
  "timer",
2562
2912
  "locale",
2563
2913
  "connection",
2564
- "remote"
2914
+ "remote",
2915
+ "workspaces",
2916
+ "sessions",
2917
+ "settingsScope",
2918
+ "webUiSettings"
2565
2919
  ];
2566
2920
  /**
2567
2921
  * 装配客户端:注册中央编辑区视图与 header 差异角标。
@@ -2570,25 +2924,90 @@ window.__ModuleLoader__.load({
2570
2924
  */
2571
2925
  function apply(ctx) {
2572
2926
  const schedule = (fn, ms) => ctx.timeout(fn, ms);
2573
- ctx.slots.inject("conversation.view", () => ctx.slots.register({
2927
+ const registry = createFileOpenerRegistry();
2928
+ const workspaces = ctx.get("workspaces");
2929
+ const sessions = ctx.get("sessions");
2930
+ const originalOpenPath = workspaces?.openPath;
2931
+ const binder = pickSettingsBinder(ctx);
2932
+ const settings = binder.scope;
2933
+ let selected = autoValue("auto");
2934
+ /** 客户端侧兼容摘要(与 host 报告合并展示;惰性求值保证 HMR 后仍新鲜)。 */
2935
+ const compatSummary = () => [
2936
+ {
2937
+ name: "设置桥",
2938
+ active: settings !== void 0,
2939
+ note: settings !== void 0 ? "使用 " + binder.service + " 桥" : "未绑定设置服务(fileOpenTool 持久化不可用)"
2940
+ },
2941
+ {
2942
+ name: "文件打开路由(workspaces.openPath)",
2943
+ active: Boolean(workspaces?.openPath),
2944
+ note: "vscode 打开器优先,失败回退系统打开"
2945
+ },
2946
+ {
2947
+ name: "侧边栏打开器(dsh-better-sidebar)",
2948
+ active: registry.get(SIDEBAR_PLUGIN) !== void 0,
2949
+ note: registry.get("dsh-better-sidebar") !== void 0 ? "已注册(优先级 80)" : "未检测到侧边栏打开能力"
2950
+ }
2951
+ ];
2952
+ ctx.provide("fileOpeners", registry);
2953
+ ctx.effect(() => registry.register(vscodeOpener()), "vscode-mode: file opener");
2954
+ ctx.effect(() => registry.register({
2955
+ id: "system",
2956
+ label: "系统默认应用",
2957
+ priority: 0,
2958
+ open: (path) => originalOpenPath.call(workspaces, path)
2959
+ }), "vscode-mode: system file opener");
2960
+ ctx.effect(() => {
2961
+ const sidebar = scanSidebar(ctx);
2962
+ return sidebar ? registry.register(sidebar) : void 0;
2963
+ }, "vscode-mode: sidebar file opener");
2964
+ ctx.effect(() => {
2965
+ if (!settings) return void 0;
2966
+ const sync = () => {
2967
+ const snapshot = settings.getSnapshot();
2968
+ if (snapshot.status === "loading") return;
2969
+ selected = autoValue(snapshot.value?.fileOpenTool);
2970
+ window.dispatchEvent(new CustomEvent("edrv:file-open-tool-change", { detail: { value: selected } }));
2971
+ };
2972
+ sync();
2973
+ return settings.subscribe(sync);
2974
+ }, "vscode-mode: file opener setting sync");
2975
+ if (workspaces?.openPath) ctx.effect(() => installOpenPathRouter({
2976
+ workspaces,
2977
+ registry,
2978
+ selected: () => selected,
2979
+ context: () => {
2980
+ const current = sessions?.list?.getSnapshot?.();
2981
+ const sessionId = current?.current;
2982
+ return {
2983
+ sessionId,
2984
+ cwd: (sessionId ? current?.byId?.[sessionId] : void 0)?.cwd
2985
+ };
2986
+ },
2987
+ logger: (message) => console.warn("[dsh-vscode-mode] " + message)
2988
+ }), "vscode-mode: file link routing");
2989
+ registerSlotSafely(ctx, {
2574
2990
  name: "conversation.view",
2575
2991
  id: "edrv-editor",
2576
2992
  order: 5,
2577
2993
  label: "文件编辑",
2578
2994
  inject: (sessionId) => ({ sessionId })
2579
- }, (props) => react.default.createElement(EditorView, Object.assign({}, props, { schedule }))));
2580
- ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({
2995
+ }, (props) => react.default.createElement(EditorView, Object.assign({}, props, { schedule })));
2996
+ registerSlotSafely(ctx, {
2581
2997
  name: "conversation.session.header.utilities",
2582
2998
  id: "edrv-diff-badge",
2583
2999
  order: 90,
2584
3000
  label: "差异"
2585
- }, (props) => react.default.createElement(DiffBadge, Object.assign({}, props))));
2586
- ctx.slots.inject("settings.section", () => ctx.slots.register({
3001
+ }, (props) => react.default.createElement(DiffBadge, Object.assign({}, props)));
3002
+ registerSlotSafely(ctx, {
2587
3003
  name: "settings.section",
2588
3004
  id: "vscode-mode",
2589
3005
  order: 30,
2590
3006
  label: "VSCodeMode"
2591
- }, () => react.default.createElement(McpSettings)));
3007
+ }, () => react.default.createElement(SettingsContext.Provider, { value: settings }, react.default.createElement(McpSettings, {
3008
+ openerRegistry: registry,
3009
+ compatSummary
3010
+ })));
2592
3011
  }
2593
3012
  //#endregion
2594
3013
  exports.apply = apply;