dsh-vscode-mode 0.14.3 → 0.14.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -9
- package/lib/client.js +488 -180
- package/lib/client.js.map +1 -1
- package/lib/index.js +242 -53
- package/lib/index.js.map +1 -1
- package/package.json +7 -7
- package/skills/dsh-vscodemode-mcp/SKILL.md +9 -3
- package/src/client/diffDockStore.ts +8 -3
- package/src/client/mcpForm.ts +79 -0
- package/src/client/mcpState.ts +179 -0
- package/src/client/styles/mcp.css +6 -3
- package/src/client/ui/McpSettings.ts +174 -137
- package/src/dshVersion.ts +4 -3
- package/src/mcp.ts +131 -23
- package/src/mcpIsolation.ts +2 -1
- package/src/mcpProject.ts +74 -14
- package/src/mcpRuntime.ts +18 -3
- package/src/rpc.ts +2 -1
- package/src/shared/mcpStatus.ts +30 -0
- package/src/shared/rpc.ts +2 -0
package/lib/client.js
CHANGED
|
@@ -8866,15 +8866,16 @@ window.__ModuleLoader__.load({
|
|
|
8866
8866
|
//#endregion
|
|
8867
8867
|
//#region src/client/diffDockStore.ts
|
|
8868
8868
|
/**
|
|
8869
|
-
*
|
|
8870
|
-
* @
|
|
8869
|
+
* 指定布局仅在仍有待处理差异时显示差异栏,冲突差异和其他文件差异也保留入口。
|
|
8870
|
+
* @public
|
|
8871
|
+
* @author ddj 2026年10月08号
|
|
8871
8872
|
* @param snapshot 编辑器发布的差异快照
|
|
8872
8873
|
* @param layout 目标布局
|
|
8873
8874
|
* @returns 是否显示差异栏
|
|
8874
8875
|
*/
|
|
8875
8876
|
function dockInLayout(snapshot, layout) {
|
|
8876
8877
|
if (!snapshot || (snapshot.layout ?? "tab") !== layout) return false;
|
|
8877
|
-
return snapshot.
|
|
8878
|
+
return Number(snapshot.fileTotal) > 0 || Number(snapshot.allPendingCount) > 0 || Number(snapshot.diffTotal) > 0 || Array.isArray(snapshot.pendingRegions) && snapshot.pendingRegions.length > 0 || Array.isArray(snapshot.staleRegions) && snapshot.staleRegions.length > 0;
|
|
8878
8879
|
}
|
|
8879
8880
|
const values = /* @__PURE__ */ new Map();
|
|
8880
8881
|
const sources = /* @__PURE__ */ new Map();
|
|
@@ -13814,7 +13815,7 @@ window.__ModuleLoader__.load({
|
|
|
13814
13815
|
}
|
|
13815
13816
|
//#endregion
|
|
13816
13817
|
//#region \0dsh-css:/home/runner/work/DSH_VsCodeMode/DSH_VsCodeMode/src/client/styles/mcp.css.mjs
|
|
13817
|
-
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/* MCP 管理下的二级子页签:我的 MCP / 项目 MCP / MCP 市场 */\n.vsm-mcp-subhead{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-top:-4px}\n.vsm-mcp-subtabs{display:flex;gap:18px;border-bottom:1px solid var(--dsw-alias-border-l1,#2e2e2e);flex:1}\n.vsm-mcp-subtabs button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);padding:7px 2px 9px;cursor:pointer;font-size:13px;position:relative}\n.vsm-mcp-subtabs button:hover{color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-mcp-subtabs button.active{color:var(--dsw-alias-label-primary,#fff);font-weight:500}\n.vsm-mcp-subtabs 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/* 顶层 Tab:通用 / MCP 管理 / 语言服务器 / AI 补全 / 性能优化 / 兼容性 */\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 用法:<section class=\"vsm-panel\"> + 可选 <h3 class=\"vsm-panel-title\">。 */\n.vsm-panel{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;background:var(--dsw-alias-bg-layer-2,transparent);overflow:hidden}\n.vsm-panel-pad{padding:14px 16px}\n.vsm-panel-title{margin:0;padding:10px 16px;font-size:13px;font-weight:600;color:var(--dsw-alias-label-secondary,#c8c8c8);background:var(--dsw-alias-bg-mask-1,#ffffff0a);border-bottom:1px solid var(--dsw-alias-border-l1,#333)}\n.vsm-panel-body{padding:6px 16px}\n.vsm-panel-body>*:first-child{margin-top:8px}\n.vsm-panel-body>*:last-child{margin-bottom:10px}\n\n/* 通用设置 */\n.vsm-general-page{display:flex;flex-direction:column;gap:14px;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;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:last-child{border-bottom:0}\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/* 兼容性条目(面板标题改用 .vsm-panel-title) */\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\n/* 开发形态开关 */\n.vsm-devform-note{display:block;margin-top:4px;font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);word-break:break-all}\n.vsm-devform-actions{display:flex;flex-direction:column;align-items:flex-end;gap:6px;min-width:0}\n.vsm-devform-msg{color:var(--dsw-alias-state-warn-primary,#e5b93f);font-size:11px;text-align:right}\n.vsm-general-row button{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:6px 12px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:12px;white-space:nowrap}\n.vsm-general-row button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-general-row button:disabled{opacity:.55;cursor:default}\n.vsm-general-row button.vsm-devform-close{color:var(--dsw-alias-state-error-primary,#f07878);border-color:var(--dsw-alias-state-error-secondary,#d9534f55)}\n\n/* 语言服务器(LSP)设置子 Tab */\n.vsm-lsp-form{display:flex;flex-direction:column;gap:6px;margin-top:8px}\n.vsm-lsp-form label{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--dsw-alias-label-secondary,#9aa5b1)}\n.vsm-lsp-form input{height:26px;padding:0 8px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-primary,#eee);font-size:12px;outline:none}\n.vsm-lsp-form input:focus{border-color:var(--dsw-alias-brand-primary,#4f8cff)}\n.vsm-lsp-form button{align-self:flex-start;margin-top:2px}\n.vsm-lsp-hint{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin-top:10px;line-height:1.6}\n\n/* 会话性能(PerfSettings 子 Tab) */\n.vsm-perf-page{display:flex;flex-direction:column;gap:14px;max-width:920px}\n.vsm-perf-page>.vsm-panel-body>*:first-child{margin-top:0}\n.vsm-perf-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-page p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}\n.vsm-perf-cards{display:flex;gap:12px;flex-wrap:wrap}\n.vsm-perf-card{display:flex;flex-direction:column;gap:4px;min-width:120px;padding:12px 16px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;background:var(--dsw-alias-bg-layer-2,transparent)}\n.vsm-perf-card span{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-card b{font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-card b.big{color:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-perf-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.vsm-perf-selcount{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:0 4px}\n/* 工作区列表分页器 */\n.vsm-perf-pager{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-top:4px}\n.vsm-perf-pager-text{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-pager-actions{display:flex;gap:8px}\n.vsm-perf-ws-list{display:flex;flex-direction:column;gap:8px}\n.vsm-perf-ws{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;overflow:hidden}\n.vsm-perf-ws-head{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--dsw-alias-bg-layer-1,#262626);cursor:pointer;user-select:none}\n.vsm-perf-ws-head:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-perf-ws-head:focus-visible{outline:1px solid var(--dsw-alias-brand-primary,#6d55ff);outline-offset:-1px}\n.vsm-perf-ws.expanded .vsm-perf-ws-head{border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}\n.vsm-perf-ws-caret{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;flex-shrink:0;transition:transform .15s;display:inline-block}\n.vsm-perf-ws-caret.open{transform:rotate(90deg)}\n.vsm-perf-ws .vsm-perf-ws-name{flex:1}\n.vsm-perf-ws-name{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6);font-family:var(--ds-font-family-code,ui-monospace,monospace);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-perf-ws-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}\n.vsm-perf-rows{display:flex;flex-direction:column}\n.vsm-perf-row{display:flex;align-items:center;gap:10px;padding:7px 14px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);font-size:12px;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-perf-row:last-child{border-bottom:0}\n.vsm-perf-row:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff0d)}\n.vsm-perf-row input[type=checkbox]{accent-color:var(--dsw-alias-brand-primary,#6d55ff);cursor:pointer}\n.vsm-perf-cell{min-width:0}\n.vsm-perf-id{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--ds-font-family-code,ui-monospace,monospace);color:var(--dsw-alias-label-primary,#eee)}\n.vsm-perf-size{width:70px;text-align:right;flex-shrink:0;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-perf-size.big{color:var(--dsw-alias-state-error-primary,#e05252);font-weight:600}\n.vsm-perf-mtime{width:44px;flex-shrink:0;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-active{width:44px;flex-shrink:0;font-size:11px;color:var(--dsw-alias-state-warn-primary,#e5b93f);text-align:right}\n.vsm-perf-config-state{display:flex;align-items:center;gap:12px;margin:10px 0}\n.vsm-perf-note{font-size:12px;color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-perf-block{margin:8px 0 0;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-secondary,#c8c8c8);font-size:11px;line-height:1.5;font-family:var(--ds-font-family-code,ui-monospace,monospace);white-space:pre-wrap;overflow-x:auto;max-width:100%}\n.vsm-perf-modal{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:var(--dsw-alias-bg-mask-1,#000000aa);z-index:1000;padding:20px}\n.vsm-perf-modal-card{width:min(560px,92vw);max-height:72vh;display:flex;flex-direction:column;gap:12px;padding:18px 20px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;background:var(--dsw-alias-bg-layer-2,#1f1f1f);color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-modal-card h3{margin:0;font-size:16px}\n.vsm-perf-modal-note{font-size:12px}\n.vsm-perf-modal-list{list-style:none;margin:0;padding:0;overflow-y:auto;font-size:12px;color:var(--dsw-alias-label-secondary,#c8c8c8);display:flex;flex-direction:column;gap:4px}\n.vsm-perf-modal-list li{padding:4px 6px;border-radius:6px;background:var(--dsw-alias-bg-layer-1,#262626);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-perf-modal-more{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-modal-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:4px}\n.vsm-secondary{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 16px;background:none;color:var(--dsw-alias-label-primary,#eee);font-size:13px;cursor:pointer;white-space:nowrap}\n.vsm-secondary:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-secondary:disabled{opacity:.55;cursor:default}\n.vsm-small{padding:4px 10px;font-size:12px}\n\n/* 扩展管理(LSP 语言服务器) */\n.vsm-mcp-actions button.vsm-primary,\n.vsm-mcp-actions button.vsm-danger{width:auto;height:auto;padding:5px 12px;font-size:12px;border-radius:6px;line-height:1.4}\n.vsm-mcp-actions button.vsm-danger{color:var(--dsw-alias-state-error-primary,#f07878);background:none}\n.vsm-mcp-actions button.vsm-danger:hover{background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}\n.vsm-ext-ver{font-weight:400;font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n";
|
|
13818
|
+
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/* MCP 管理下的二级子页签:我的 MCP / 项目 MCP / MCP 市场 */\n.vsm-mcp-subhead{display:flex;align-items:center;justify-content:space-between;gap:16px;margin-top:-4px}\n.vsm-mcp-subtabs{display:flex;gap:18px;border-bottom:1px solid var(--dsw-alias-border-l1,#2e2e2e);flex:1}\n.vsm-mcp-subtabs button{border:0;background:none;color:var(--dsw-alias-label-tertiary,#9aa5b1);padding:7px 2px 9px;cursor:pointer;font-size:13px;position:relative}\n.vsm-mcp-subtabs button:hover{color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-mcp-subtabs button.active{color:var(--dsw-alias-label-primary,#fff);font-weight:500}\n.vsm-mcp-subtabs 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/* 顶层 Tab:通用 / MCP 管理 / 语言服务器 / AI 补全 / 性能优化 / 兼容性 */\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 用法:<section class=\"vsm-panel\"> + 可选 <h3 class=\"vsm-panel-title\">。 */\n.vsm-panel{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;background:var(--dsw-alias-bg-layer-2,transparent);overflow:hidden}\n.vsm-panel-pad{padding:14px 16px}\n.vsm-panel-title{margin:0;padding:10px 16px;font-size:13px;font-weight:600;color:var(--dsw-alias-label-secondary,#c8c8c8);background:var(--dsw-alias-bg-mask-1,#ffffff0a);border-bottom:1px solid var(--dsw-alias-border-l1,#333)}\n.vsm-panel-body{padding:6px 16px}\n.vsm-panel-body>*:first-child{margin-top:8px}\n.vsm-panel-body>*:last-child{margin-bottom:10px}\n\n/* 通用设置 */\n.vsm-general-page{display:flex;flex-direction:column;gap:14px;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;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:last-child{border-bottom:0}\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-label-tertiary,#9aa5b1)}\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.error{background:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-mcp-dot.disabled,.vsm-mcp-dot.neutral,.vsm-mcp-dot.configured,.vsm-mcp-dot.unverified{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/* 配置启用使用品牌色,不冒充在线状态;在线证据仅由 connected 圆点表达。 */\n.vsm-switch.on{color:var(--dsw-alias-brand-primary,#6d55ff)}\n.vsm-mcp-actions button:disabled,.vsm-mcp-dialog-actions button:disabled{opacity:.55;cursor:default}\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/* 兼容性条目(面板标题改用 .vsm-panel-title) */\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\n/* 开发形态开关 */\n.vsm-devform-note{display:block;margin-top:4px;font-size:11px;color:var(--dsw-alias-label-tertiary,#9aa5b1);word-break:break-all}\n.vsm-devform-actions{display:flex;flex-direction:column;align-items:flex-end;gap:6px;min-width:0}\n.vsm-devform-msg{color:var(--dsw-alias-state-warn-primary,#e5b93f);font-size:11px;text-align:right}\n.vsm-general-row button{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:6px 12px;background:none;color:var(--dsw-alias-label-primary,#eee);cursor:pointer;font-size:12px;white-space:nowrap}\n.vsm-general-row button:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-general-row button:disabled{opacity:.55;cursor:default}\n.vsm-general-row button.vsm-devform-close{color:var(--dsw-alias-state-error-primary,#f07878);border-color:var(--dsw-alias-state-error-secondary,#d9534f55)}\n\n/* 语言服务器(LSP)设置子 Tab */\n.vsm-lsp-form{display:flex;flex-direction:column;gap:6px;margin-top:8px}\n.vsm-lsp-form label{display:flex;flex-direction:column;gap:4px;font-size:12px;color:var(--dsw-alias-label-secondary,#9aa5b1)}\n.vsm-lsp-form input{height:26px;padding:0 8px;border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-primary,#eee);font-size:12px;outline:none}\n.vsm-lsp-form input:focus{border-color:var(--dsw-alias-brand-primary,#4f8cff)}\n.vsm-lsp-form button{align-self:flex-start;margin-top:2px}\n.vsm-lsp-hint{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin-top:10px;line-height:1.6}\n\n/* 会话性能(PerfSettings 子 Tab) */\n.vsm-perf-page{display:flex;flex-direction:column;gap:14px;max-width:920px}\n.vsm-perf-page>.vsm-panel-body>*:first-child{margin-top:0}\n.vsm-perf-page h2{margin:0;font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-page p{margin:0;color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:13px;line-height:1.5}\n.vsm-perf-cards{display:flex;gap:12px;flex-wrap:wrap}\n.vsm-perf-card{display:flex;flex-direction:column;gap:4px;min-width:120px;padding:12px 16px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;background:var(--dsw-alias-bg-layer-2,transparent)}\n.vsm-perf-card span{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-card b{font-size:20px;color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-card b.big{color:var(--dsw-alias-state-error-primary,#e05252)}\n.vsm-perf-toolbar{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.vsm-perf-selcount{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);margin:0 4px}\n/* 工作区列表分页器 */\n.vsm-perf-pager{display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-top:4px}\n.vsm-perf-pager-text{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-pager-actions{display:flex;gap:8px}\n.vsm-perf-ws-list{display:flex;flex-direction:column;gap:8px}\n.vsm-perf-ws{border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:10px;overflow:hidden}\n.vsm-perf-ws-head{display:flex;align-items:center;gap:10px;padding:10px 14px;background:var(--dsw-alias-bg-layer-1,#262626);cursor:pointer;user-select:none}\n.vsm-perf-ws-head:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-perf-ws-head:focus-visible{outline:1px solid var(--dsw-alias-brand-primary,#6d55ff);outline-offset:-1px}\n.vsm-perf-ws.expanded .vsm-perf-ws-head{border-bottom:1px solid var(--dsw-alias-border-l2,#3a3a3a)}\n.vsm-perf-ws-caret{color:var(--dsw-alias-label-tertiary,#9aa5b1);font-size:12px;flex-shrink:0;transition:transform .15s;display:inline-block}\n.vsm-perf-ws-caret.open{transform:rotate(90deg)}\n.vsm-perf-ws .vsm-perf-ws-name{flex:1}\n.vsm-perf-ws-name{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary,#e6e6e6);font-family:var(--ds-font-family-code,ui-monospace,monospace);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-perf-ws-meta{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);flex-shrink:0}\n.vsm-perf-rows{display:flex;flex-direction:column}\n.vsm-perf-row{display:flex;align-items:center;gap:10px;padding:7px 14px;border-bottom:1px solid var(--dsw-alias-border-l1,#333);font-size:12px;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-perf-row:last-child{border-bottom:0}\n.vsm-perf-row:hover{background:var(--dsw-alias-interactive-bg-hover,#ffffff0d)}\n.vsm-perf-row input[type=checkbox]{accent-color:var(--dsw-alias-brand-primary,#6d55ff);cursor:pointer}\n.vsm-perf-cell{min-width:0}\n.vsm-perf-id{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-family:var(--ds-font-family-code,ui-monospace,monospace);color:var(--dsw-alias-label-primary,#eee)}\n.vsm-perf-size{width:70px;text-align:right;flex-shrink:0;color:var(--dsw-alias-label-secondary,#c8c8c8)}\n.vsm-perf-size.big{color:var(--dsw-alias-state-error-primary,#e05252);font-weight:600}\n.vsm-perf-mtime{width:44px;flex-shrink:0;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-active{width:44px;flex-shrink:0;font-size:11px;color:var(--dsw-alias-state-warn-primary,#e5b93f);text-align:right}\n.vsm-perf-config-state{display:flex;align-items:center;gap:12px;margin:10px 0}\n.vsm-perf-note{font-size:12px;color:var(--dsw-alias-state-warn-primary,#e5b93f)}\n.vsm-perf-block{margin:8px 0 0;padding:10px 12px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:8px;background:var(--dsw-alias-bg-base,#111);color:var(--dsw-alias-label-secondary,#c8c8c8);font-size:11px;line-height:1.5;font-family:var(--ds-font-family-code,ui-monospace,monospace);white-space:pre-wrap;overflow-x:auto;max-width:100%}\n.vsm-perf-modal{position:fixed;inset:0;display:flex;align-items:center;justify-content:center;background:var(--dsw-alias-bg-mask-1,#000000aa);z-index:1000;padding:20px}\n.vsm-perf-modal-card{width:min(560px,92vw);max-height:72vh;display:flex;flex-direction:column;gap:12px;padding:18px 20px;border:1px solid var(--dsw-alias-border-l2,#3a3a3a);border-radius:12px;background:var(--dsw-alias-bg-layer-2,#1f1f1f);color:var(--dsw-alias-label-primary,#e6e6e6)}\n.vsm-perf-modal-card h3{margin:0;font-size:16px}\n.vsm-perf-modal-note{font-size:12px}\n.vsm-perf-modal-list{list-style:none;margin:0;padding:0;overflow-y:auto;font-size:12px;color:var(--dsw-alias-label-secondary,#c8c8c8);display:flex;flex-direction:column;gap:4px}\n.vsm-perf-modal-list li{padding:4px 6px;border-radius:6px;background:var(--dsw-alias-bg-layer-1,#262626);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n.vsm-perf-modal-more{font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1)}\n.vsm-perf-modal-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:4px}\n.vsm-secondary{border:1px solid var(--dsw-alias-border-l2,#555);border-radius:6px;padding:8px 16px;background:none;color:var(--dsw-alias-label-primary,#eee);font-size:13px;cursor:pointer;white-space:nowrap}\n.vsm-secondary:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#ffffff14)}\n.vsm-secondary:disabled{opacity:.55;cursor:default}\n.vsm-small{padding:4px 10px;font-size:12px}\n\n/* 扩展管理(LSP 语言服务器) */\n.vsm-mcp-actions button.vsm-primary,\n.vsm-mcp-actions button.vsm-danger{width:auto;height:auto;padding:5px 12px;font-size:12px;border-radius:6px;line-height:1.4}\n.vsm-mcp-actions button.vsm-danger{color:var(--dsw-alias-state-error-primary,#f07878);background:none}\n.vsm-mcp-actions button.vsm-danger:hover{background:var(--dsw-alias-interactive-bg-hover-danger,#ff000014)}\n.vsm-ext-ver{font-weight:400;font-size:12px;color:var(--dsw-alias-label-tertiary,#9aa5b1);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n";
|
|
13818
13819
|
const tagId = "dsh-vscode-mode/mcp.css";
|
|
13819
13820
|
if (typeof document !== "undefined" && document.querySelector("style[data-plugin-css=" + JSON.stringify(tagId) + "]") === null) {
|
|
13820
13821
|
const tag = document.createElement("style");
|
|
@@ -24649,6 +24650,276 @@ window.__ModuleLoader__.load({
|
|
|
24649
24650
|
}), sessionId || "chat"), palette);
|
|
24650
24651
|
}
|
|
24651
24652
|
//#endregion
|
|
24653
|
+
//#region src/client/mcpForm.ts
|
|
24654
|
+
/**
|
|
24655
|
+
* @public
|
|
24656
|
+
* @author ddj 2026年10月08号
|
|
24657
|
+
* @description Parse lossless JSON string[] or one literal argument per nonempty line; no shell splitting.
|
|
24658
|
+
* @param text Argument field.
|
|
24659
|
+
* @returns Literal argument vector.
|
|
24660
|
+
* @throws Invalid JSON or a non-string array element.
|
|
24661
|
+
*/
|
|
24662
|
+
function parseArgs(text) {
|
|
24663
|
+
if (text.trimStart().startsWith("[")) {
|
|
24664
|
+
let value;
|
|
24665
|
+
try {
|
|
24666
|
+
value = JSON.parse(text);
|
|
24667
|
+
} catch {
|
|
24668
|
+
throw new Error("参数 JSON 无效,请输入 string[] 或每行一个参数");
|
|
24669
|
+
}
|
|
24670
|
+
if (!Array.isArray(value) || !value.every((item) => typeof item === "string")) throw new Error("参数 JSON 必须是 string[]");
|
|
24671
|
+
return value;
|
|
24672
|
+
}
|
|
24673
|
+
return text.split(/\r?\n/).filter((line) => line.length > 0);
|
|
24674
|
+
}
|
|
24675
|
+
/**
|
|
24676
|
+
* @public
|
|
24677
|
+
* @author ddj 2026年10月08号
|
|
24678
|
+
* @description Validate HTTP headers; duplicates are case-insensitive and values retain embedded equals.
|
|
24679
|
+
* @param text One key=value header per line.
|
|
24680
|
+
* @returns Validated header dictionary.
|
|
24681
|
+
* @throws Missing separator, invalid/empty key, duplicate key, or illegal control characters.
|
|
24682
|
+
*/
|
|
24683
|
+
function parsePairs(text) {
|
|
24684
|
+
const pairs = [];
|
|
24685
|
+
const keys = /* @__PURE__ */ new Set();
|
|
24686
|
+
for (const [index, raw] of text.split(/\r?\n/).entries()) {
|
|
24687
|
+
if (!raw.trim()) continue;
|
|
24688
|
+
const separator = raw.indexOf("=");
|
|
24689
|
+
const key = raw.slice(0, separator).trim();
|
|
24690
|
+
const value = raw.slice(separator + 1).trim();
|
|
24691
|
+
if (separator < 1 || !/^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/.test(key) || /[\x00-\x08\x0a-\x1f\x7f]/.test(value)) throw new Error("请求头第 " + (index + 1) + " 行格式错误,应为有效 key=value");
|
|
24692
|
+
const normalized = key.toLowerCase();
|
|
24693
|
+
if (keys.has(normalized)) throw new Error("请求头重复:" + key);
|
|
24694
|
+
keys.add(normalized);
|
|
24695
|
+
pairs.push([key, value]);
|
|
24696
|
+
}
|
|
24697
|
+
return Object.fromEntries(pairs);
|
|
24698
|
+
}
|
|
24699
|
+
/**
|
|
24700
|
+
* @public
|
|
24701
|
+
* @author ddj 2026年10月08号
|
|
24702
|
+
* @description Convert a form draft without losing argument boundaries or silently accepting malformed headers.
|
|
24703
|
+
* @param draft Current editable fields.
|
|
24704
|
+
* @returns Host MCP configuration.
|
|
24705
|
+
* @throws Argument/header validation errors; callers must keep the form open.
|
|
24706
|
+
*/
|
|
24707
|
+
function configOf(draft) {
|
|
24708
|
+
const config = {
|
|
24709
|
+
serverName: draft.serverName.trim(),
|
|
24710
|
+
transport: draft.transport
|
|
24711
|
+
};
|
|
24712
|
+
if (draft.transport === "stdio") {
|
|
24713
|
+
config.command = draft.command.trim();
|
|
24714
|
+
config.args = parseArgs(draft.args);
|
|
24715
|
+
if (draft.cwd.trim()) config.cwd = draft.cwd.trim();
|
|
24716
|
+
} else {
|
|
24717
|
+
config.url = draft.url.trim();
|
|
24718
|
+
config.headers = parsePairs(draft.headers);
|
|
24719
|
+
}
|
|
24720
|
+
return config;
|
|
24721
|
+
}
|
|
24722
|
+
//#endregion
|
|
24723
|
+
//#region src/client/mcpState.ts
|
|
24724
|
+
/** Owns MCP read lifetimes and one synchronous mutation lock for the entire management page. */
|
|
24725
|
+
var McpState = class {
|
|
24726
|
+
request;
|
|
24727
|
+
changed;
|
|
24728
|
+
view = {
|
|
24729
|
+
servers: [],
|
|
24730
|
+
projects: [],
|
|
24731
|
+
loading: true,
|
|
24732
|
+
busy: "",
|
|
24733
|
+
error: ""
|
|
24734
|
+
};
|
|
24735
|
+
active = false;
|
|
24736
|
+
visible = true;
|
|
24737
|
+
disposed = false;
|
|
24738
|
+
version = 0;
|
|
24739
|
+
reading = null;
|
|
24740
|
+
timer = null;
|
|
24741
|
+
interval;
|
|
24742
|
+
timeout;
|
|
24743
|
+
/** @public @author ddj 2026年10月08号 @param request Typed RPC transport. @param changed View notification. @param timing Bounded read and polling timings. */
|
|
24744
|
+
constructor(request, changed, timing = {}) {
|
|
24745
|
+
this.request = request;
|
|
24746
|
+
this.changed = changed;
|
|
24747
|
+
this.interval = Math.max(100, timing.interval ?? 2500);
|
|
24748
|
+
this.timeout = Math.max(50, timing.timeout ?? 1e4);
|
|
24749
|
+
}
|
|
24750
|
+
/** @private @author ddj 2026年10月08号 @description Publish a copied view only while mounted. */
|
|
24751
|
+
emit() {
|
|
24752
|
+
if (!this.disposed) this.changed({ ...this.view });
|
|
24753
|
+
}
|
|
24754
|
+
/** @private @author ddj 2026年10月08号 @description Cancel scheduled reads and invalidate every older response. */
|
|
24755
|
+
invalidate() {
|
|
24756
|
+
this.version++;
|
|
24757
|
+
if (this.timer !== null) clearTimeout(this.timer);
|
|
24758
|
+
this.timer = null;
|
|
24759
|
+
this.reading?.controller.abort();
|
|
24760
|
+
}
|
|
24761
|
+
/** @private @author ddj 2026年10月08号 @description Schedule a single read only after the preceding transport settled. */
|
|
24762
|
+
schedule() {
|
|
24763
|
+
if (this.disposed || !this.active || !this.visible || this.reading || this.view.loading || this.view.busy || this.timer !== null) return;
|
|
24764
|
+
this.timer = setTimeout(this.poll, this.interval);
|
|
24765
|
+
}
|
|
24766
|
+
/** @private @author ddj 2026年10月08号 @description Timer entry, with failures handled in read. */
|
|
24767
|
+
poll = () => {
|
|
24768
|
+
this.timer = null;
|
|
24769
|
+
this.read(false);
|
|
24770
|
+
};
|
|
24771
|
+
/** @private @author ddj 2026年10月08号 @returns Versioned read token with an abort deadline. */
|
|
24772
|
+
beginRead() {
|
|
24773
|
+
const controller = new AbortController();
|
|
24774
|
+
const version = this.version;
|
|
24775
|
+
/** @author ddj 2026年10月08号 @description Abort timed-out transport and invalidate its eventual response. */
|
|
24776
|
+
const expire = () => {
|
|
24777
|
+
controller.abort();
|
|
24778
|
+
if (this.disposed || version !== this.version) return;
|
|
24779
|
+
this.version++;
|
|
24780
|
+
this.view.loading = false;
|
|
24781
|
+
this.view.error = "读取 MCP 状态超时";
|
|
24782
|
+
this.emit();
|
|
24783
|
+
};
|
|
24784
|
+
const token = {
|
|
24785
|
+
controller,
|
|
24786
|
+
version,
|
|
24787
|
+
timer: setTimeout(expire, this.timeout)
|
|
24788
|
+
};
|
|
24789
|
+
this.reading = token;
|
|
24790
|
+
return token;
|
|
24791
|
+
}
|
|
24792
|
+
/** @private @author ddj 2026年10月08号 @param initial Initialization versus read-only polling. @param signal Abort deadline. @returns Complete settled data, never an early half-settled initial read. @throws Transport or host failures. */
|
|
24793
|
+
async fetchData(initial, signal) {
|
|
24794
|
+
if (!initial) {
|
|
24795
|
+
const snapshot = await this.request("mcp.snapshot", {}, signal);
|
|
24796
|
+
if (!snapshot.ok) throw new Error(snapshot.error);
|
|
24797
|
+
return snapshot;
|
|
24798
|
+
}
|
|
24799
|
+
const [list, groups] = await Promise.allSettled([this.request("mcp.list", {}, signal), this.request("mcp.projects", {}, signal)]);
|
|
24800
|
+
if (list.status === "rejected") throw list.reason;
|
|
24801
|
+
if (groups.status === "rejected") throw groups.reason;
|
|
24802
|
+
if (!list.value.ok) throw new Error(list.value.error);
|
|
24803
|
+
if (!groups.value.ok) throw new Error(groups.value.error);
|
|
24804
|
+
return {
|
|
24805
|
+
servers: list.value.servers,
|
|
24806
|
+
projects: groups.value.projects
|
|
24807
|
+
};
|
|
24808
|
+
}
|
|
24809
|
+
/** @private @author ddj 2026年10月08号 @param initial Use legacy initialization RPCs once; later polls are read-only snapshots. @returns Completion after transport settles; aborted results never publish. */
|
|
24810
|
+
async read(initial) {
|
|
24811
|
+
if (this.disposed || this.reading || this.view.busy) return;
|
|
24812
|
+
if (!initial && (!this.active || !this.visible)) return;
|
|
24813
|
+
const token = this.beginRead();
|
|
24814
|
+
try {
|
|
24815
|
+
const signal = token.controller.signal;
|
|
24816
|
+
const { servers, projects } = await this.fetchData(initial, signal);
|
|
24817
|
+
if (!this.disposed && token.version === this.version && !signal.aborted) {
|
|
24818
|
+
this.view.servers = servers;
|
|
24819
|
+
this.view.projects = projects;
|
|
24820
|
+
this.view.error = "";
|
|
24821
|
+
}
|
|
24822
|
+
} catch (error) {
|
|
24823
|
+
if (!this.disposed && token.version === this.version && !token.controller.signal.aborted) this.view.error = String(error);
|
|
24824
|
+
} finally {
|
|
24825
|
+
clearTimeout(token.timer);
|
|
24826
|
+
this.reading = null;
|
|
24827
|
+
if (!this.disposed) {
|
|
24828
|
+
this.view.loading = false;
|
|
24829
|
+
this.emit();
|
|
24830
|
+
this.schedule();
|
|
24831
|
+
}
|
|
24832
|
+
}
|
|
24833
|
+
}
|
|
24834
|
+
/** @public @author ddj 2026年10月08号 @description Initialize list/projects once per mounted page. @returns Load completion. */
|
|
24835
|
+
load() {
|
|
24836
|
+
return this.read(true);
|
|
24837
|
+
}
|
|
24838
|
+
/** @public @author ddj 2026年10月08号 @param active Whether the top-level MCP tab is selected. */
|
|
24839
|
+
setActive(active) {
|
|
24840
|
+
if (this.active === active) return;
|
|
24841
|
+
this.active = active;
|
|
24842
|
+
if (!active) this.invalidate();
|
|
24843
|
+
this.schedule();
|
|
24844
|
+
}
|
|
24845
|
+
/** @public @author ddj 2026年10月08号 @param visible Whether document visibility permits background requests. */
|
|
24846
|
+
setVisible(visible) {
|
|
24847
|
+
if (this.visible === visible) return;
|
|
24848
|
+
this.visible = visible;
|
|
24849
|
+
if (!visible) this.invalidate();
|
|
24850
|
+
this.schedule();
|
|
24851
|
+
}
|
|
24852
|
+
/** @public @author ddj 2026年10月08号 @description Stop timers and invalidate all responses on unmount. */
|
|
24853
|
+
dispose() {
|
|
24854
|
+
this.disposed = true;
|
|
24855
|
+
this.invalidate();
|
|
24856
|
+
if (this.reading) clearTimeout(this.reading.timer);
|
|
24857
|
+
}
|
|
24858
|
+
/**
|
|
24859
|
+
* @public
|
|
24860
|
+
* @author ddj 2026年10月08号
|
|
24861
|
+
* @description Acquire a synchronous page-wide lock before validation/RPC. Duplicate actions are rejected; failed saves never call commit.
|
|
24862
|
+
* @param label Busy indicator key.
|
|
24863
|
+
* @param task Validation and RPC work, including synchronous configuration errors.
|
|
24864
|
+
* @param commit Apply successful result; may reset and close a save form.
|
|
24865
|
+
* @returns Whether this mutation committed successfully while mounted.
|
|
24866
|
+
*/
|
|
24867
|
+
async mutate(label, task, commit) {
|
|
24868
|
+
if (this.disposed || this.view.busy) return false;
|
|
24869
|
+
this.invalidate();
|
|
24870
|
+
this.view.busy = label;
|
|
24871
|
+
this.view.error = "";
|
|
24872
|
+
this.emit();
|
|
24873
|
+
try {
|
|
24874
|
+
const result = await task();
|
|
24875
|
+
if (!result.ok) throw new Error(result.error);
|
|
24876
|
+
if (this.disposed) return false;
|
|
24877
|
+
commit(result);
|
|
24878
|
+
return true;
|
|
24879
|
+
} catch (error) {
|
|
24880
|
+
if (!this.disposed) this.view.error = String(error);
|
|
24881
|
+
return false;
|
|
24882
|
+
} finally {
|
|
24883
|
+
if (!this.disposed) {
|
|
24884
|
+
this.view.busy = "";
|
|
24885
|
+
this.emit();
|
|
24886
|
+
this.schedule();
|
|
24887
|
+
}
|
|
24888
|
+
}
|
|
24889
|
+
}
|
|
24890
|
+
};
|
|
24891
|
+
//#endregion
|
|
24892
|
+
//#region src/shared/mcpStatus.ts
|
|
24893
|
+
/**
|
|
24894
|
+
* @public
|
|
24895
|
+
* @author ddj 2026年10月08号
|
|
24896
|
+
* @description Display lifecycle evidence; enabled only expresses configuration intent, not connectivity.
|
|
24897
|
+
* @param server Server lifecycle snapshot.
|
|
24898
|
+
* @returns User-facing status label.
|
|
24899
|
+
*/
|
|
24900
|
+
function statusOf(server) {
|
|
24901
|
+
if (!server.enabled || server.status === "disabled") return "已禁用";
|
|
24902
|
+
return {
|
|
24903
|
+
connected: "在线",
|
|
24904
|
+
connecting: "装配中",
|
|
24905
|
+
configured: "已配置,等待会话",
|
|
24906
|
+
unverified: "已加载,连接未确认",
|
|
24907
|
+
error: "错误"
|
|
24908
|
+
}[server.status] ?? "状态未知";
|
|
24909
|
+
}
|
|
24910
|
+
/**
|
|
24911
|
+
* @public
|
|
24912
|
+
* @author ddj 2026年10月08号
|
|
24913
|
+
* @description Use neutral styling unless lifecycle evidence explicitly identifies online, loading or error.
|
|
24914
|
+
* @param server Server lifecycle snapshot.
|
|
24915
|
+
* @returns CSS tone, independent of configuration-enabled state.
|
|
24916
|
+
*/
|
|
24917
|
+
function statusTone(server) {
|
|
24918
|
+
if (!server.enabled || server.status === "disabled") return "disabled";
|
|
24919
|
+
if (server.status === "connected" || server.status === "connecting" || server.status === "error") return server.status;
|
|
24920
|
+
return "neutral";
|
|
24921
|
+
}
|
|
24922
|
+
//#endregion
|
|
24652
24923
|
//#region src/client/settingsContext.ts
|
|
24653
24924
|
/**
|
|
24654
24925
|
* dsh-vscode-mode client — 设置 scope 的跨组件上下文。
|
|
@@ -26594,53 +26865,24 @@ window.__ModuleLoader__.load({
|
|
|
26594
26865
|
url: "",
|
|
26595
26866
|
headers: ""
|
|
26596
26867
|
};
|
|
26597
|
-
/**
|
|
26598
|
-
function
|
|
26599
|
-
|
|
26600
|
-
|
|
26601
|
-
transport: draft.transport
|
|
26602
|
-
};
|
|
26603
|
-
if (draft.transport === "stdio") {
|
|
26604
|
-
config.command = draft.command.trim();
|
|
26605
|
-
config.args = draft.args.split(/\r?\n|\s+/).filter(Boolean);
|
|
26606
|
-
if (draft.cwd.trim()) config.cwd = draft.cwd.trim();
|
|
26607
|
-
} else {
|
|
26608
|
-
config.url = draft.url.trim();
|
|
26609
|
-
config.headers = parsePairs(draft.headers);
|
|
26610
|
-
}
|
|
26611
|
-
return config;
|
|
26612
|
-
}
|
|
26613
|
-
/** 解析每行 key=value 的表单字段。 */
|
|
26614
|
-
function parsePairs(text) {
|
|
26615
|
-
return Object.fromEntries(text.split(/\r?\n/).map((line) => line.trim()).filter(Boolean).map((line) => {
|
|
26616
|
-
const idx = line.indexOf("=");
|
|
26617
|
-
return idx > 0 ? [line.slice(0, idx).trim(), line.slice(idx + 1).trim()] : [line, ""];
|
|
26618
|
-
}));
|
|
26619
|
-
}
|
|
26620
|
-
/** @author ddj 2026年09月28号 @param server Server lifecycle snapshot. @returns User-facing status. */
|
|
26621
|
-
function statusOf(server) {
|
|
26622
|
-
if (!server.enabled || server.status === "disabled") return "已禁用";
|
|
26623
|
-
return {
|
|
26624
|
-
connected: "在线",
|
|
26625
|
-
connecting: "装配中",
|
|
26626
|
-
configured: "已配置,等待会话",
|
|
26627
|
-
unverified: "已加载,连接未确认",
|
|
26628
|
-
error: "错误"
|
|
26629
|
-
}[server.status] ?? "状态未知";
|
|
26630
|
-
}
|
|
26631
|
-
/** MCP 服务卡片(全局与项目共用)。 */
|
|
26632
|
-
function ServerCard({ server, onRefresh, onToggle, onRemove }) {
|
|
26633
|
-
return react.default.createElement("article", { className: "vsm-mcp-card" }, react.default.createElement("div", { className: "vsm-mcp-card-head" }, react.default.createElement("div", { className: "vsm-mcp-title" }, react.default.createElement("span", { className: "vsm-mcp-dot " + (server.enabled ? server.status : "disabled") }), server.serverName), react.default.createElement("div", { className: "vsm-mcp-actions" }, react.default.createElement("button", {
|
|
26868
|
+
/** @private @author ddj 2026年10月08号 @param props Server snapshot, page-wide busy state and actions. @returns Lifecycle-evidence card; enabled does not imply online. */
|
|
26869
|
+
function ServerCard({ server, busy, onRefresh, onToggle, onRemove }) {
|
|
26870
|
+
return react.default.createElement("article", { className: "vsm-mcp-card" }, react.default.createElement("div", { className: "vsm-mcp-card-head" }, react.default.createElement("div", { className: "vsm-mcp-title" }, react.default.createElement("span", { className: "vsm-mcp-dot " + statusTone(server) }), server.serverName), react.default.createElement("div", { className: "vsm-mcp-actions" }, react.default.createElement("button", {
|
|
26871
|
+
disabled: !!busy,
|
|
26634
26872
|
onClick: () => onRefresh(server.id),
|
|
26635
|
-
title: "
|
|
26873
|
+
title: "重新装配会话实例"
|
|
26636
26874
|
}, "↻"), react.default.createElement("button", {
|
|
26875
|
+
disabled: !!busy,
|
|
26637
26876
|
className: "vsm-danger",
|
|
26638
26877
|
onClick: () => onRemove(server.id),
|
|
26639
26878
|
title: "删除 MCP"
|
|
26640
26879
|
}, "⌫"), react.default.createElement("button", {
|
|
26880
|
+
disabled: !!busy,
|
|
26641
26881
|
className: "vsm-switch " + (server.enabled ? "on" : ""),
|
|
26642
26882
|
onClick: () => onToggle(server),
|
|
26643
|
-
|
|
26883
|
+
title: "配置启用不代表连接在线",
|
|
26884
|
+
"aria-label": server.enabled ? "禁用配置(不代表在线)" : "启用配置(不代表在线)",
|
|
26885
|
+
"aria-pressed": !!server.enabled
|
|
26644
26886
|
}, server.enabled ? "●" : "○"))), react.default.createElement("div", { className: "vsm-mcp-meta" }, statusOf(server), " · ", server.toolCount, " 个工具 · ", server.transport, server.instanceCount !== void 0 ? " · " + server.instanceCount + " 个会话实例(各自连接)" : ""), server.error && react.default.createElement("div", { className: "vsm-mcp-error" }, server.error), react.default.createElement("div", { className: "vsm-mcp-tools" }, server.tools.map((tool) => react.default.createElement("span", {
|
|
26645
26887
|
key: tool.name,
|
|
26646
26888
|
className: "vsm-mcp-chip",
|
|
@@ -26719,10 +26961,11 @@ window.__ModuleLoader__.load({
|
|
|
26719
26961
|
onClick: () => choose(project)
|
|
26720
26962
|
}, react.default.createElement("span", null, project.title), react.default.createElement("small", null, project.workspacePath, " · ", project.servers.length, " 个 MCP"))) : react.default.createElement("div", { className: "vsm-project-picker-empty" }, "没有匹配的项目"))));
|
|
26721
26963
|
}
|
|
26722
|
-
/**
|
|
26964
|
+
/** @private @author ddj 2026年10月08号 @param props Project view, shared busy lock and actions. @returns Project cards with actions disabled for any active mutation. */
|
|
26723
26965
|
function ProjectGroup({ project, busy, onAdd, onRefresh, onToggle, onRemove }) {
|
|
26724
26966
|
const head = react.default.createElement("div", { className: "vsm-project-head" }, react.default.createElement("div", { className: "vsm-project-title" }, react.default.createElement("span", { className: "vsm-project-icon" }, "▣"), react.default.createElement("div", null, react.default.createElement("span", { className: "vsm-project-name" }, project.title), react.default.createElement("span", { className: "vsm-project-path" }, project.workspacePath))), react.default.createElement("button", {
|
|
26725
26967
|
className: "vsm-primary vsm-small",
|
|
26968
|
+
disabled: !!busy,
|
|
26726
26969
|
onClick: () => onAdd(project)
|
|
26727
26970
|
}, "+ 添加 MCP"));
|
|
26728
26971
|
let body;
|
|
@@ -26732,6 +26975,7 @@ window.__ModuleLoader__.load({
|
|
|
26732
26975
|
else body = project.servers.map((server) => react.default.createElement(ServerCard, {
|
|
26733
26976
|
key: server.serverName,
|
|
26734
26977
|
server,
|
|
26978
|
+
busy,
|
|
26735
26979
|
onRefresh: (id) => onRefresh(project, server, id),
|
|
26736
26980
|
onToggle: () => onToggle(project, server),
|
|
26737
26981
|
onRemove: (id) => onRemove(project, server, id)
|
|
@@ -27084,8 +27328,10 @@ window.__ModuleLoader__.load({
|
|
|
27084
27328
|
* @param {object} draft 新增表单草稿
|
|
27085
27329
|
* @param {Function} edit 草稿字段变更回调
|
|
27086
27330
|
* @param {Function} resetDraft 清空草稿回调
|
|
27331
|
+
* @author ddj 2026年10月08号
|
|
27087
27332
|
*/
|
|
27088
|
-
function McpManagePanel(
|
|
27333
|
+
function McpManagePanel(props) {
|
|
27334
|
+
const { projects, busy, draft, edit, resetDraft, saveGlobal, saveProject } = props;
|
|
27089
27335
|
const [mcpTab, setMcpTab] = react.default.useState("mine");
|
|
27090
27336
|
const [showForm, setShowForm] = react.default.useState(false);
|
|
27091
27337
|
const [projectForm, setProjectForm] = react.default.useState(null);
|
|
@@ -27097,39 +27343,28 @@ window.__ModuleLoader__.load({
|
|
|
27097
27343
|
}
|
|
27098
27344
|
if (!projects.some((project) => project.workspacePath === selectedPath)) setSelectedPath(projects[0].workspacePath);
|
|
27099
27345
|
}, [projects, selectedPath]);
|
|
27346
|
+
/** @author ddj 2026年10月08号 @description Close the successful global form. */
|
|
27100
27347
|
const closeGlobalForm = () => setShowForm(false);
|
|
27348
|
+
/** @author ddj 2026年10月08号 @description Close the successful project form. */
|
|
27101
27349
|
const closeProjectForm = () => setProjectForm(null);
|
|
27350
|
+
/** @author ddj 2026年10月08号 @param project Target project. @description Open a project draft only when idle. */
|
|
27102
27351
|
const addProject = (project) => {
|
|
27352
|
+
if (busy) return;
|
|
27103
27353
|
resetDraft?.();
|
|
27354
|
+
setShowForm(false);
|
|
27104
27355
|
setProjectForm({
|
|
27105
27356
|
workspacePath: project.workspacePath,
|
|
27106
27357
|
title: project.title
|
|
27107
27358
|
});
|
|
27108
27359
|
};
|
|
27109
|
-
|
|
27110
|
-
const
|
|
27111
|
-
|
|
27112
|
-
|
|
27113
|
-
|
|
27114
|
-
|
|
27115
|
-
|
|
27116
|
-
|
|
27117
|
-
const projectBody = projects.length ? react.default.createElement(react.default.Fragment, null, react.default.createElement(ProjectPicker, {
|
|
27118
|
-
projects,
|
|
27119
|
-
value: selectedPath,
|
|
27120
|
-
onChange: setSelectedPath
|
|
27121
|
-
}), selectedProject ? react.default.createElement(ProjectGroup, {
|
|
27122
|
-
project: selectedProject,
|
|
27123
|
-
busy,
|
|
27124
|
-
onAdd: addProject,
|
|
27125
|
-
onRefresh: refreshProject,
|
|
27126
|
-
onToggle: toggleProject,
|
|
27127
|
-
onRemove: removeProject
|
|
27128
|
-
}) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "请选择项目")) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有项目");
|
|
27129
|
-
let body;
|
|
27130
|
-
if (mcpTab === "projects") body = projectBody;
|
|
27131
|
-
else if (mcpTab === "market") body = react.default.createElement("div", { className: "vsm-mcp-empty" }, "MCP 市场暂未接入");
|
|
27132
|
-
else body = mineBody;
|
|
27360
|
+
/** @author ddj 2026年10月08号 @description Open a global draft only when idle. */
|
|
27361
|
+
const addGlobal = () => {
|
|
27362
|
+
if (busy) return;
|
|
27363
|
+
resetDraft?.();
|
|
27364
|
+
setProjectForm(null);
|
|
27365
|
+
setShowForm(true);
|
|
27366
|
+
};
|
|
27367
|
+
const body = manageBody(props, mcpTab, selectedPath, setSelectedPath, addProject);
|
|
27133
27368
|
const form = showForm ? react.default.createElement(McpForm, {
|
|
27134
27369
|
title: "添加全局 MCP",
|
|
27135
27370
|
draft,
|
|
@@ -27151,112 +27386,177 @@ window.__ModuleLoader__.load({
|
|
|
27151
27386
|
onClick: () => setMcpTab(item.id)
|
|
27152
27387
|
}, item.label))), react.default.createElement("button", {
|
|
27153
27388
|
className: "vsm-primary vsm-small",
|
|
27154
|
-
|
|
27155
|
-
|
|
27156
|
-
setShowForm(true);
|
|
27157
|
-
}
|
|
27389
|
+
disabled: !!busy,
|
|
27390
|
+
onClick: addGlobal
|
|
27158
27391
|
}, "+ 添加全局 MCP")), body, form);
|
|
27159
27392
|
}
|
|
27160
|
-
/** MCP
|
|
27393
|
+
/** @private @author ddj 2026年10月08号 @param props Current view and actions. @param tab Selected MCP subtab. @param path Selected project. @param select Project setter. @param add Project form opener. @returns Selected global/project/market content. */
|
|
27394
|
+
function manageBody(props, tab, path, select, add) {
|
|
27395
|
+
const { servers, projects, busy, refreshGlobal, toggleGlobal, removeGlobal, refreshProject, toggleProject, removeProject } = props;
|
|
27396
|
+
if (tab === "market") return react.default.createElement("div", { className: "vsm-mcp-empty" }, "MCP 市场暂未接入");
|
|
27397
|
+
if (tab !== "projects") return servers.length ? servers.map((server) => react.default.createElement(ServerCard, {
|
|
27398
|
+
key: server.id,
|
|
27399
|
+
server,
|
|
27400
|
+
busy,
|
|
27401
|
+
onRefresh: refreshGlobal,
|
|
27402
|
+
onToggle: toggleGlobal,
|
|
27403
|
+
onRemove: removeGlobal
|
|
27404
|
+
})) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有配置全局 MCP");
|
|
27405
|
+
if (!projects.length) return react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有项目");
|
|
27406
|
+
const project = projects.find((item) => item.workspacePath === path);
|
|
27407
|
+
return react.default.createElement(react.default.Fragment, null, react.default.createElement(ProjectPicker, {
|
|
27408
|
+
projects,
|
|
27409
|
+
value: path,
|
|
27410
|
+
onChange: select
|
|
27411
|
+
}), project ? react.default.createElement(ProjectGroup, {
|
|
27412
|
+
project,
|
|
27413
|
+
busy,
|
|
27414
|
+
onAdd: add,
|
|
27415
|
+
onRefresh: refreshProject,
|
|
27416
|
+
onToggle: toggleProject,
|
|
27417
|
+
onRemove: removeProject
|
|
27418
|
+
}) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "请选择项目"));
|
|
27419
|
+
}
|
|
27420
|
+
/** @private @author ddj 2026年10月08号 @returns Mounted controller reference and its published view. */
|
|
27421
|
+
function useMcpState() {
|
|
27422
|
+
const ref = react.default.useRef(null);
|
|
27423
|
+
const [view, setView] = react.default.useState({
|
|
27424
|
+
servers: [],
|
|
27425
|
+
projects: [],
|
|
27426
|
+
loading: true,
|
|
27427
|
+
busy: "",
|
|
27428
|
+
error: ""
|
|
27429
|
+
});
|
|
27430
|
+
/** @author ddj 2026年10月08号 @description Subscribe to document visibility and initialize MCP data. @returns Unmount cleanup. */
|
|
27431
|
+
const mount = () => {
|
|
27432
|
+
const state = new McpState(rpc, setView);
|
|
27433
|
+
ref.current = state;
|
|
27434
|
+
/** @author ddj 2026年10月08号 @description Gate snapshots on actual document visibility. */
|
|
27435
|
+
const visibility = () => state.setVisible(document.visibilityState === "visible");
|
|
27436
|
+
/** @author ddj 2026年10月08号 @description Stop controller and remove page listener. */
|
|
27437
|
+
const cleanup = () => {
|
|
27438
|
+
document.removeEventListener("visibilitychange", visibility);
|
|
27439
|
+
state.dispose();
|
|
27440
|
+
if (ref.current === state) ref.current = null;
|
|
27441
|
+
};
|
|
27442
|
+
visibility();
|
|
27443
|
+
document.addEventListener("visibilitychange", visibility);
|
|
27444
|
+
state.load();
|
|
27445
|
+
return cleanup;
|
|
27446
|
+
};
|
|
27447
|
+
react.default.useEffect(mount, []);
|
|
27448
|
+
return {
|
|
27449
|
+
ref,
|
|
27450
|
+
view
|
|
27451
|
+
};
|
|
27452
|
+
}
|
|
27453
|
+
/** @private @author ddj 2026年10月08号 @param state Mounted controller. @param id Server identity. @param method Mutation RPC. @param args Request fields. @returns Locked completion. */
|
|
27454
|
+
function globalAction(state, id, method, args) {
|
|
27455
|
+
/** @author ddj 2026年10月08号 @returns Global mutation result. */
|
|
27456
|
+
const request = () => rpc(method, args);
|
|
27457
|
+
/** @author ddj 2026年10月08号 @param result Global mutation result. @description Apply only successful mutation to the latest controller view. */
|
|
27458
|
+
const commit = (result) => {
|
|
27459
|
+
state.view.servers = method === "mcp.remove" ? state.view.servers.filter((server) => server.id !== id) : state.view.servers.map((server) => server.id === id ? result.server : server);
|
|
27460
|
+
};
|
|
27461
|
+
return state?.mutate(id, request, commit);
|
|
27462
|
+
}
|
|
27463
|
+
/** @private @author ddj 2026年10月08号 @param state Mounted controller. @param path Project path. @param name Server identity. @param method Mutation RPC. @param extra Optional request fields. @returns Locked completion. */
|
|
27464
|
+
function projectAction(state, path, name, method, extra = {}) {
|
|
27465
|
+
/** @author ddj 2026年10月08号 @returns Project mutation result. */
|
|
27466
|
+
const request = () => rpc(method, {
|
|
27467
|
+
workspacePath: path,
|
|
27468
|
+
serverName: name,
|
|
27469
|
+
...extra
|
|
27470
|
+
});
|
|
27471
|
+
/** @author ddj 2026年10月08号 @param result Project mutation result. @description Replace project in the current view. */
|
|
27472
|
+
const commit = (result) => {
|
|
27473
|
+
state.view.projects = state.view.projects.map((project) => project.workspacePath === path ? result.project : project);
|
|
27474
|
+
};
|
|
27475
|
+
return state?.mutate(path + ":" + name, request, commit);
|
|
27476
|
+
}
|
|
27477
|
+
/** @private @author ddj 2026年10月08号 @param ref Mounted controller reference. @returns Global/project card actions sharing a synchronous lock. */
|
|
27478
|
+
function cardActions(ref) {
|
|
27479
|
+
/** @author ddj 2026年10月08号 @param id Global identity. @returns Locked refresh. */
|
|
27480
|
+
const refreshGlobal = (id) => globalAction(ref.current, id, "mcp.refresh", { id });
|
|
27481
|
+
/** @author ddj 2026年10月08号 @param server Current configuration. @returns Locked enable change. */
|
|
27482
|
+
const toggleGlobal = (server) => globalAction(ref.current, server.id, "mcp.toggle", {
|
|
27483
|
+
id: server.id,
|
|
27484
|
+
enabled: !server.enabled
|
|
27485
|
+
});
|
|
27486
|
+
/** @author ddj 2026年10月08号 @param id Global identity from ServerCard. @returns Confirmed locked deletion. */
|
|
27487
|
+
const removeGlobal = (id) => !ref.current?.view.busy && window.confirm("确认删除此 MCP 服务?") && globalAction(ref.current, id, "mcp.remove", { id });
|
|
27488
|
+
/** @author ddj 2026年10月08号 @param project Project. @param server Configuration. @returns Locked refresh. */
|
|
27489
|
+
const refreshProject = (project, server) => projectAction(ref.current, project.workspacePath, server.serverName, "mcp.projectRefresh");
|
|
27490
|
+
/** @author ddj 2026年10月08号 @param project Project. @param server Configuration. @returns Locked enable change. */
|
|
27491
|
+
const toggleProject = (project, server) => projectAction(ref.current, project.workspacePath, server.serverName, "mcp.projectToggle", { enabled: !server.enabled });
|
|
27492
|
+
/** @author ddj 2026年10月08号 @param project Project. @param server Configuration. @returns Confirmed locked deletion. */
|
|
27493
|
+
const removeProject = (project, server) => !ref.current?.view.busy && window.confirm("确认删除此项目的 MCP「" + server.serverName + "」?") && projectAction(ref.current, project.workspacePath, server.serverName, "mcp.projectRemove");
|
|
27494
|
+
return {
|
|
27495
|
+
refreshGlobal,
|
|
27496
|
+
toggleGlobal,
|
|
27497
|
+
removeGlobal,
|
|
27498
|
+
refreshProject,
|
|
27499
|
+
toggleProject,
|
|
27500
|
+
removeProject
|
|
27501
|
+
};
|
|
27502
|
+
}
|
|
27503
|
+
/** @private @author ddj 2026年10月08号 @param state Controller. @param draft Editable fields. @param setDraft Draft setter. @param path Optional project target. @param close Success-only close callback. @returns Locked save, validation included. */
|
|
27504
|
+
function saveDraft(state, draft, setDraft, path, close) {
|
|
27505
|
+
/** @author ddj 2026年10月08号 @returns Validated save result. @throws Malformed arguments/headers. */
|
|
27506
|
+
const request = () => {
|
|
27507
|
+
const config = configOf(draft);
|
|
27508
|
+
return path === null ? rpc("mcp.save", { config }) : rpc("mcp.projectSave", {
|
|
27509
|
+
workspacePath: path,
|
|
27510
|
+
serverName: config.serverName,
|
|
27511
|
+
config
|
|
27512
|
+
});
|
|
27513
|
+
};
|
|
27514
|
+
/** @author ddj 2026年10月08号 @param result Save result. @description Update current view and only then reset/close the form. */
|
|
27515
|
+
const commit = (result) => {
|
|
27516
|
+
if (path !== null) state.view.projects = state.view.projects.map((project) => project.workspacePath === path ? result.project : project);
|
|
27517
|
+
else {
|
|
27518
|
+
const servers = state.view.servers;
|
|
27519
|
+
state.view.servers = servers.some((server) => server.id === result.server.id) ? servers.map((server) => server.id === result.server.id ? result.server : server) : servers.concat(result.server);
|
|
27520
|
+
}
|
|
27521
|
+
setDraft(EMPTY);
|
|
27522
|
+
close();
|
|
27523
|
+
};
|
|
27524
|
+
return state?.mutate(path === null ? "save" : "project-save", request, commit);
|
|
27525
|
+
}
|
|
27526
|
+
/** @public @author ddj 2026年10月08号 @param props Settings integrations. @returns MCP management page with visible-tab-only snapshot polling. */
|
|
27161
27527
|
function McpSettings(props) {
|
|
27162
27528
|
const openerRegistry = props?.openerRegistry ?? { list: () => [] };
|
|
27163
27529
|
const compatSummary = props?.compatSummary ?? (() => []);
|
|
27164
27530
|
const getShortcuts = props?.getShortcuts ?? (() => null);
|
|
27165
|
-
const
|
|
27166
|
-
const
|
|
27167
|
-
const [loading, setLoading] = react.default.useState(true);
|
|
27168
|
-
const [busy, setBusy] = react.default.useState("");
|
|
27169
|
-
const [error, setError] = react.default.useState("");
|
|
27531
|
+
const { ref, view } = useMcpState();
|
|
27532
|
+
const { servers, projects, loading, busy, error } = view;
|
|
27170
27533
|
const [tab, setTab] = react.default.useState("general");
|
|
27171
27534
|
const [draft, setDraft] = react.default.useState(EMPTY);
|
|
27172
|
-
|
|
27173
|
-
|
|
27174
|
-
|
|
27175
|
-
|
|
27176
|
-
|
|
27177
|
-
|
|
27178
|
-
|
|
27179
|
-
|
|
27180
|
-
|
|
27181
|
-
|
|
27182
|
-
|
|
27183
|
-
const
|
|
27184
|
-
|
|
27185
|
-
|
|
27186
|
-
|
|
27187
|
-
const
|
|
27188
|
-
|
|
27189
|
-
|
|
27190
|
-
|
|
27191
|
-
|
|
27192
|
-
}).catch((e) => setError(String(e))).finally(() => setBusy("")).then(() => label);
|
|
27193
|
-
};
|
|
27194
|
-
const refreshGlobal = (id) => action("refresh", id, "mcp.refresh", { id }, (r) => setServers((old) => old.map((s) => s.id === id ? r.server : s)));
|
|
27195
|
-
const toggleGlobal = (s) => action("toggle", s.id, "mcp.toggle", {
|
|
27196
|
-
id: s.id,
|
|
27197
|
-
enabled: !s.enabled
|
|
27198
|
-
}, (r) => setServers((old) => old.map((x) => x.id === s.id ? r.server : x)));
|
|
27199
|
-
const removeGlobal = (s) => window.confirm("确认删除此 MCP 服务?") && action("remove", s.id, "mcp.remove", { id: s.id }, (r) => setServers((old) => old.filter((x) => x.id !== s.id)));
|
|
27200
|
-
const replaceProject = (workspacePath) => (result) => setProjects((old) => old.map((p) => p.workspacePath === workspacePath ? result.project : p));
|
|
27201
|
-
const projectAction = (workspacePath, serverName, method, args, patch) => {
|
|
27202
|
-
setBusy(workspacePath + ":" + serverName);
|
|
27203
|
-
setError("");
|
|
27204
|
-
return rpc(method, args).then(finish).then((result) => {
|
|
27205
|
-
if (patch) patch(result);
|
|
27206
|
-
}).catch((e) => setError(String(e))).finally(() => setBusy(""));
|
|
27535
|
+
/** @author ddj 2026年10月08号 @description Select polling scope and invalidate late responses on tab leave. @returns Tab cleanup. */
|
|
27536
|
+
const selectTab = () => {
|
|
27537
|
+
const state = ref.current;
|
|
27538
|
+
state?.setActive(tab === "mcp");
|
|
27539
|
+
/** @author ddj 2026年10月08号 @description Cancel reads on tab switch. */
|
|
27540
|
+
const cleanup = () => state?.setActive(false);
|
|
27541
|
+
return cleanup;
|
|
27542
|
+
};
|
|
27543
|
+
react.default.useEffect(selectTab, [tab]);
|
|
27544
|
+
const { refreshGlobal, toggleGlobal, removeGlobal, refreshProject, toggleProject, removeProject } = cardActions(ref);
|
|
27545
|
+
/** @author ddj 2026年10月08号 @param close Success-only close callback. @returns Global save completion. */
|
|
27546
|
+
const saveGlobal = (close) => saveDraft(ref.current, draft, setDraft, null, close);
|
|
27547
|
+
/** @author ddj 2026年10月08号 @param path Target project. @param close Success-only close callback. @returns Project save completion. */
|
|
27548
|
+
const saveProject = (path, close) => saveDraft(ref.current, draft, setDraft, path, close);
|
|
27549
|
+
/** @author ddj 2026年10月08号 @param key Draft field. @param value User input. @description Keep draft edits unavailable during a save. */
|
|
27550
|
+
const edit = (key, value) => {
|
|
27551
|
+
if (!ref.current?.view.busy) setDraft((old) => ({
|
|
27552
|
+
...old,
|
|
27553
|
+
[key]: value
|
|
27554
|
+
}));
|
|
27207
27555
|
};
|
|
27208
|
-
|
|
27209
|
-
|
|
27210
|
-
|
|
27211
|
-
}, replaceProject(p.workspacePath));
|
|
27212
|
-
const toggleProject = (p, s) => projectAction(p.workspacePath, s.serverName, "mcp.projectToggle", {
|
|
27213
|
-
workspacePath: p.workspacePath,
|
|
27214
|
-
serverName: s.serverName,
|
|
27215
|
-
enabled: !s.enabled
|
|
27216
|
-
}, replaceProject(p.workspacePath));
|
|
27217
|
-
const removeProject = (p, s) => window.confirm("确认删除此项目的 MCP「" + s.serverName + "」?") && projectAction(p.workspacePath, s.serverName, "mcp.projectRemove", {
|
|
27218
|
-
workspacePath: p.workspacePath,
|
|
27219
|
-
serverName: s.serverName
|
|
27220
|
-
}, replaceProject(p.workspacePath));
|
|
27221
|
-
/**
|
|
27222
|
-
* 保存全局 MCP:成功后由回调关闭子面板。
|
|
27223
|
-
* 作者 ddj 2026年08月27号
|
|
27224
|
-
* @param {Function} close 关闭新增表单回调
|
|
27225
|
-
*/
|
|
27226
|
-
const saveGlobal = (close) => {
|
|
27227
|
-
setBusy("save");
|
|
27228
|
-
setError("");
|
|
27229
|
-
rpc("mcp.save", { config: configOf(draft) }).then(finish).then((result) => {
|
|
27230
|
-
setServers((old) => old.some((s) => s.id === result.server.id) ? old.map((s) => s.id === result.server.id ? result.server : s) : old.concat(result.server));
|
|
27231
|
-
setDraft(EMPTY);
|
|
27232
|
-
close();
|
|
27233
|
-
}).catch((e) => setError(String(e))).finally(() => setBusy(""));
|
|
27556
|
+
/** @author ddj 2026年10月08号 @description Reset the draft when no mutation is pending. */
|
|
27557
|
+
const resetDraft = () => {
|
|
27558
|
+
if (!ref.current?.view.busy) setDraft(EMPTY);
|
|
27234
27559
|
};
|
|
27235
|
-
/**
|
|
27236
|
-
* 保存项目 MCP:成功后由回调关闭子面板。
|
|
27237
|
-
* 作者 ddj 2026年08月27号
|
|
27238
|
-
* @param {string} workspacePath 目标项目路径
|
|
27239
|
-
* @param {Function} close 关闭新增表单回调
|
|
27240
|
-
*/
|
|
27241
|
-
const saveProject = (workspacePath, close) => {
|
|
27242
|
-
setBusy("project-save");
|
|
27243
|
-
setError("");
|
|
27244
|
-
const config = configOf(draft);
|
|
27245
|
-
rpc("mcp.projectSave", {
|
|
27246
|
-
workspacePath,
|
|
27247
|
-
serverName: config.serverName,
|
|
27248
|
-
config
|
|
27249
|
-
}).then(finish).then((result) => {
|
|
27250
|
-
setProjects((old) => old.map((p) => p.workspacePath === workspacePath ? result.project : p));
|
|
27251
|
-
setDraft(EMPTY);
|
|
27252
|
-
close();
|
|
27253
|
-
}).catch((e) => setError(String(e))).finally(() => setBusy(""));
|
|
27254
|
-
};
|
|
27255
|
-
const edit = (key, value) => setDraft((old) => ({
|
|
27256
|
-
...old,
|
|
27257
|
-
[key]: value
|
|
27258
|
-
}));
|
|
27259
|
-
const resetDraft = () => setDraft(EMPTY);
|
|
27260
27560
|
let body;
|
|
27261
27561
|
if (loading) body = react.default.createElement("div", { className: "vsm-mcp-empty" }, "正在读取 MCP 服务…");
|
|
27262
27562
|
else if (tab === "general") body = react.default.createElement(GeneralSettings, {
|
|
@@ -27270,8 +27570,8 @@ window.__ModuleLoader__.load({
|
|
|
27270
27570
|
draft,
|
|
27271
27571
|
edit,
|
|
27272
27572
|
resetDraft,
|
|
27273
|
-
saveGlobal
|
|
27274
|
-
saveProject
|
|
27573
|
+
saveGlobal,
|
|
27574
|
+
saveProject,
|
|
27275
27575
|
refreshGlobal,
|
|
27276
27576
|
toggleGlobal,
|
|
27277
27577
|
removeGlobal,
|
|
@@ -27303,28 +27603,36 @@ window.__ModuleLoader__.load({
|
|
|
27303
27603
|
onClick: () => setTab("compat")
|
|
27304
27604
|
}, "兼容性")), error && react.default.createElement("div", { className: "vsm-mcp-error vsm-mcp-banner" }, error), body);
|
|
27305
27605
|
}
|
|
27306
|
-
/**
|
|
27606
|
+
/** @private @author ddj 2026年10月08号 @param props Draft, shared busy lock, edit/save/close callbacks. @returns Lossless argument/header form; failed save stays open. */
|
|
27307
27607
|
function McpForm({ title, draft, busy, edit, save, close }) {
|
|
27608
|
+
/** @author ddj 2026年10月08号 @param key Draft field. @returns Short input change callback. */
|
|
27609
|
+
const change = (key) => (event) => edit(key, event.target.value);
|
|
27610
|
+
/** @author ddj 2026年10月08号 @param key Draft field. @param placeholder Input hint. @returns Disabled-while-busy input. */
|
|
27308
27611
|
const input = (key, placeholder) => react.default.createElement("input", {
|
|
27612
|
+
disabled: !!busy,
|
|
27309
27613
|
value: draft[key],
|
|
27310
|
-
onChange: (
|
|
27614
|
+
onChange: change(key),
|
|
27311
27615
|
placeholder
|
|
27312
27616
|
});
|
|
27313
|
-
|
|
27314
|
-
|
|
27315
|
-
|
|
27316
|
-
|
|
27317
|
-
|
|
27318
|
-
|
|
27319
|
-
|
|
27617
|
+
/** @author ddj 2026年10月08号 @param key Draft field. @returns Disabled-while-busy text area. */
|
|
27618
|
+
const textarea = (key) => react.default.createElement("textarea", {
|
|
27619
|
+
disabled: !!busy,
|
|
27620
|
+
value: draft[key],
|
|
27621
|
+
onChange: change(key)
|
|
27622
|
+
});
|
|
27623
|
+
const fields = draft.transport === "stdio" ? react.default.createElement(react.default.Fragment, null, react.default.createElement("label", null, "命令", input("command", "npx")), react.default.createElement("label", null, "参数(JSON string[] 或每行一个;保留空格,空参数用 JSON)", textarea("args")), react.default.createElement("label", null, "工作目录(可选)", input("cwd", ""))) : react.default.createElement(react.default.Fragment, null, react.default.createElement("label", null, "URL", input("url", "http://localhost:3000/mcp")), react.default.createElement("label", null, "请求头(每行 key=value,键不可重复)", textarea("headers")));
|
|
27320
27624
|
return react.default.createElement("div", { className: "vsm-mcp-modal" }, react.default.createElement("div", { className: "vsm-mcp-dialog" }, react.default.createElement("h3", null, title), react.default.createElement("label", null, "名称", input("serverName", "例如 codegraph")), react.default.createElement("label", null, "传输方式", react.default.createElement("select", {
|
|
27625
|
+
disabled: !!busy,
|
|
27321
27626
|
value: draft.transport,
|
|
27322
|
-
onChange: (
|
|
27323
|
-
}, react.default.createElement("option", { value: "stdio" }, "stdio"), react.default.createElement("option", { value: "streamable-http" }, "streamable-http"))), fields, react.default.createElement("div", { className: "vsm-mcp-dialog-actions" }, react.default.createElement("button", {
|
|
27627
|
+
onChange: change("transport")
|
|
27628
|
+
}, react.default.createElement("option", { value: "stdio" }, "stdio"), react.default.createElement("option", { value: "streamable-http" }, "streamable-http"))), fields, react.default.createElement("div", { className: "vsm-mcp-dialog-actions" }, react.default.createElement("button", {
|
|
27629
|
+
disabled: !!busy,
|
|
27630
|
+
onClick: close
|
|
27631
|
+
}, "取消"), react.default.createElement("button", {
|
|
27324
27632
|
className: "vsm-primary",
|
|
27325
|
-
disabled: busy
|
|
27633
|
+
disabled: !!busy,
|
|
27326
27634
|
onClick: save
|
|
27327
|
-
}, "
|
|
27635
|
+
}, "保存配置"))));
|
|
27328
27636
|
}
|
|
27329
27637
|
//#endregion
|
|
27330
27638
|
//#region src/client/openPathRouter.ts
|