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/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
- * @author ddj 2026年09月29号
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.mode !== "editor-empty" || Boolean(snapshot.fileTotal);
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
- /** 将表单草稿转换为 Host MCP 配置。 */
26598
- function configOf(draft) {
26599
- const config = {
26600
- serverName: draft.serverName.trim(),
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
- "aria-label": server.enabled ? "禁用" : "启用"
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
- /** 单个项目的 MCP 分组。 */
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({ servers, projects, busy, draft, edit, resetDraft, refreshGlobal, toggleGlobal, removeGlobal, refreshProject, toggleProject, removeProject, saveGlobal, saveProject }) {
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
- const selectedProject = projects.find((project) => project.workspacePath === selectedPath);
27110
- const mineBody = servers.length ? servers.map((s) => react.default.createElement(ServerCard, {
27111
- key: s.id,
27112
- server: s,
27113
- onRefresh: refreshGlobal,
27114
- onToggle: toggleGlobal,
27115
- onRemove: removeGlobal
27116
- })) : react.default.createElement("div", { className: "vsm-mcp-empty" }, "还没有配置全局 MCP");
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
- onClick: () => {
27155
- resetDraft?.();
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 [servers, setServers] = react.default.useState([]);
27166
- const [projects, setProjects] = react.default.useState([]);
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
- const loadAll = react.default.useCallback(() => {
27173
- setLoading(true);
27174
- setError("");
27175
- Promise.all([rpc("mcp.list", {}), rpc("mcp.projects", {})]).then(([list, prj]) => {
27176
- if (!list.ok) throw new Error(list.error);
27177
- if (!prj.ok) throw new Error(prj.error);
27178
- setServers(list.servers);
27179
- setProjects(prj.projects);
27180
- }).catch((e) => setError(String(e))).finally(() => setLoading(false));
27181
- }, []);
27182
- react.default.useEffect(loadAll, [loadAll]);
27183
- const finish = (result) => {
27184
- if (!result.ok) throw new Error(result.error);
27185
- return result;
27186
- };
27187
- const action = (label, id, method, args, patch) => {
27188
- setBusy(id);
27189
- setError("");
27190
- return rpc(method, args).then(finish).then((result) => {
27191
- if (patch) patch(result);
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
- const refreshProject = (p, s) => projectAction(p.workspacePath, s.serverName, "mcp.projectRefresh", {
27209
- workspacePath: p.workspacePath,
27210
- serverName: s.serverName
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: (close) => saveGlobal(close),
27274
- saveProject: (workspacePath, close) => saveProject(workspacePath, close),
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
- /** MCP 新增表单(全局/项目共用)。 */
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: (e) => edit(key, e.target.value),
27614
+ onChange: change(key),
27311
27615
  placeholder
27312
27616
  });
27313
- 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, "参数(空格或换行分隔)", react.default.createElement("textarea", {
27314
- value: draft.args,
27315
- onChange: (e) => edit("args", e.target.value)
27316
- })), 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)", react.default.createElement("textarea", {
27317
- value: draft.headers,
27318
- onChange: (e) => edit("headers", e.target.value)
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: (e) => edit("transport", e.target.value)
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", { onClick: close }, "取消"), 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 === "save" || busy === "project-save",
27633
+ disabled: !!busy,
27326
27634
  onClick: save
27327
- }, "保存并连接"))));
27635
+ }, "保存配置"))));
27328
27636
  }
27329
27637
  //#endregion
27330
27638
  //#region src/client/openPathRouter.ts