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 +13 -3
- package/lib/client.js +432 -13
- package/lib/client.js.map +1 -1
- package/lib/index.js +418 -131
- package/lib/index.js.map +1 -1
- package/package.json +6 -1
- package/src/capture.ts +4 -10
- package/src/client/compat.ts +91 -0
- package/src/client/fileOpeners.ts +173 -0
- package/src/client/index.ts +64 -9
- package/src/client/openPathRouter.ts +57 -0
- package/src/client/settingsContext.ts +21 -0
- package/src/client/styles/mcp.css +16 -0
- package/src/client/ui/EditorView.ts +5 -2
- package/src/client/ui/McpSettings.ts +76 -3
- package/src/compat.ts +146 -0
- package/src/fileOpenSettings.ts +124 -0
- package/src/host-deps.d.ts +14 -0
- package/src/index.ts +35 -5
- package/src/mcp.ts +8 -9
- package/src/mcpIsolation.ts +6 -12
- package/src/routes.ts +26 -7
- package/src/rpc.ts +20 -0
- package/src/shared/compat.ts +20 -0
- package/src/shared/rpc.ts +7 -0
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)
|
|
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("
|
|
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 市场")
|
|
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
|
-
|
|
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
|
|
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
|
|
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;
|