@sunshaobei/dsh-mcp-manager 0.0.0-stage → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -0
- package/README.en.md +476 -0
- package/README.md +379 -2
- package/cordis.patch.yml +9 -0
- package/lib/THIRD-PARTY-LICENSES +43 -0
- package/lib/client/core/api.d.ts +42 -0
- package/lib/client/core/constants.d.ts +58 -0
- package/lib/client/core/current-session.d.ts +39 -0
- package/lib/client/core/dom.d.ts +19 -0
- package/lib/client/core/i18n.d.ts +12 -0
- package/lib/client/core/session.d.ts +27 -0
- package/lib/client/core/state.d.ts +120 -0
- package/lib/client/float/float.d.ts +126 -0
- package/lib/client/float/panel.d.ts +29 -0
- package/lib/client/float/quick-add.d.ts +38 -0
- package/lib/client/float/servers.d.ts +54 -0
- package/lib/client/index.d.ts +26 -0
- package/lib/client/locales.d.ts +123 -0
- package/lib/client/settings/settings-card.d.ts +47 -0
- package/lib/client.js +2568 -0
- package/lib/index.d.ts +86 -0
- package/lib/index.js +10035 -0
- package/lib/server/api/deps.d.ts +36 -0
- package/lib/server/api/impl/health/type.d.ts +12 -0
- package/lib/server/api/impl/service/index.d.ts +28 -0
- package/lib/server/api/interface.d.ts +27 -0
- package/lib/server/api/routes-controllers.d.ts +61 -0
- package/lib/server/api/routes-helpers.d.ts +8 -0
- package/lib/server/api/routes.d.ts +58 -0
- package/lib/server/catalog/deps.d.ts +51 -0
- package/lib/server/catalog/impl/cache-view/index.d.ts +48 -0
- package/lib/server/catalog/impl/digest/index.d.ts +19 -0
- package/lib/server/catalog/impl/directory/index.d.ts +168 -0
- package/lib/server/catalog/impl/entries/index.d.ts +129 -0
- package/lib/server/catalog/impl/entries/type.d.ts +19 -0
- package/lib/server/catalog/impl/history/index.d.ts +36 -0
- package/lib/server/catalog/impl/injection/index.d.ts +45 -0
- package/lib/server/catalog/impl/search/type.d.ts +59 -0
- package/lib/server/catalog/impl/service/index.d.ts +25 -0
- package/lib/server/catalog/interface.d.ts +45 -0
- package/lib/server/catalog/search.d.ts +108 -0
- package/lib/server/config/config-schema.d.ts +85 -0
- package/lib/server/config/impl/env/index.d.ts +24 -0
- package/lib/server/config/impl/env/policy.d.ts +54 -0
- package/lib/server/config/impl/model/type.d.ts +39 -0
- package/lib/server/config/impl/ui/type.d.ts +20 -0
- package/lib/server/config/import.d.ts +20 -0
- package/lib/server/config/interface.d.ts +15 -0
- package/lib/server/config/normalize.d.ts +24 -0
- package/lib/server/config/sources-sync.d.ts +32 -0
- package/lib/server/config/sources.d.ts +50 -0
- package/lib/server/connection/interface.d.ts +86 -0
- package/lib/server/connection/orchestrator/deps.d.ts +93 -0
- package/lib/server/connection/orchestrator/impl/service/index.d.ts +27 -0
- package/lib/server/connection/orchestrator/interface.d.ts +25 -0
- package/lib/server/connection/orchestrator/manager.d.ts +484 -0
- package/lib/server/connection/orchestrator/tool-names.d.ts +14 -0
- package/lib/server/connection/runtime/deps.d.ts +141 -0
- package/lib/server/connection/runtime/impl/middleware/type.d.ts +45 -0
- package/lib/server/connection/runtime/impl/service/index.d.ts +28 -0
- package/lib/server/connection/runtime/interface.d.ts +29 -0
- package/lib/server/connection/runtime/limits.d.ts +19 -0
- package/lib/server/connection/runtime/middleware.d.ts +194 -0
- package/lib/server/erasure/impl/guard/index.d.ts +35 -0
- package/lib/server/erasure/interface.d.ts +31 -0
- package/lib/server/inject/deps.d.ts +54 -0
- package/lib/server/inject/impl/call-timeout/index.d.ts +1 -0
- package/lib/server/inject/impl/image-admission/index.d.ts +81 -0
- package/lib/server/inject/impl/service/index.d.ts +25 -0
- package/lib/server/inject/interface.d.ts +27 -0
- package/lib/server/inject/middleware-register.d.ts +114 -0
- package/lib/server/pipeline/deps.d.ts +27 -0
- package/lib/server/pipeline/impl/args/index.d.ts +26 -0
- package/lib/server/pipeline/impl/authorize/index.d.ts +43 -0
- package/lib/server/pipeline/impl/msg/index.d.ts +8 -0
- package/lib/server/pipeline/impl/project/index.d.ts +43 -0
- package/lib/server/pipeline/impl/redact/index.d.ts +13 -0
- package/lib/server/pipeline/impl/service/index.d.ts +25 -0
- package/lib/server/pipeline/impl/timeout/index.d.ts +8 -0
- package/lib/server/pipeline/interface.d.ts +32 -0
- package/lib/server/servers/dispatch/deps.d.ts +100 -0
- package/lib/server/servers/dispatch/impl/call/index.d.ts +49 -0
- package/lib/server/servers/dispatch/impl/redact/index.d.ts +13 -0
- package/lib/server/servers/dispatch/interface.d.ts +18 -0
- package/lib/server/servers/lifecycle/deps.d.ts +62 -0
- package/lib/server/servers/lifecycle/impl/ledger/index.d.ts +52 -0
- package/lib/server/servers/lifecycle/impl/logs/index.d.ts +48 -0
- package/lib/server/servers/lifecycle/impl/mount/index.d.ts +83 -0
- package/lib/server/servers/lifecycle/impl/service/index.d.ts +26 -0
- package/lib/server/servers/lifecycle/impl/state/index.d.ts +35 -0
- package/lib/server/servers/lifecycle/impl/timeout/index.d.ts +39 -0
- package/lib/server/servers/lifecycle/interface.d.ts +56 -0
- package/lib/server/shared/compose.d.ts +38 -0
- package/lib/server/shared/constants.d.ts +62 -0
- package/lib/server/shared/file-io.d.ts +8 -0
- package/lib/server/shared/host-faces.d.ts +206 -0
- package/lib/server/shared/interface.d.ts +14 -0
- package/lib/server/shared/paths.d.ts +66 -0
- package/lib/server/shared/tool-names.d.ts +11 -0
- package/lib/server/stats/impl/collector.d.ts +68 -0
- package/lib/server/stats/impl/type.d.ts +41 -0
- package/lib/server/stats/interface.d.ts +13 -0
- package/lib/server/store/impl/middleware-state.d.ts +45 -0
- package/lib/server/store/impl/store.d.ts +43 -0
- package/lib/server/store/interface.d.ts +9 -0
- package/lib/server/upgrade/deps.d.ts +25 -0
- package/lib/server/upgrade/impl/chain/index.d.ts +2 -0
- package/lib/server/upgrade/impl/service/index.d.ts +9 -0
- package/lib/server/upgrade/impl/steps/index.d.ts +19 -0
- package/lib/server/upgrade/impl/steps/project-layout.d.ts +24 -0
- package/lib/server/upgrade/impl/steps/storage-layout.d.ts +17 -0
- package/lib/server/upgrade/impl/version/index.d.ts +7 -0
- package/lib/server/upgrade/interface.d.ts +21 -0
- package/lib/server/visibility/impl/mask/index.d.ts +7 -0
- package/lib/server/visibility/interface.d.ts +46 -0
- package/lib/server/workspace/impl/full-name/index.d.ts +24 -0
- package/lib/server/workspace/impl/root-resolution/index.d.ts +18 -0
- package/lib/server/workspace/impl/scope/index.d.ts +14 -0
- package/lib/server/workspace/impl/server-id/index.d.ts +17 -0
- package/lib/server/workspace/interface.d.ts +22 -0
- package/lib/shared/constants.d.ts +22 -0
- package/lib/shared/dto.d.ts +62 -0
- package/lib/shared/frames.d.ts +19 -0
- package/lib/shared/interface.d.ts +22 -0
- package/lib/shared/placement-math.d.ts +81 -0
- package/lib/shared/routes.d.ts +43 -0
- package/lib/shared/service.d.ts +85 -0
- package/lib/shared/status.d.ts +23 -0
- package/lib/vendor/shared/README.md +147 -0
- package/lib/vendor/shared/client/ensure-style.d.ts +23 -0
- package/lib/vendor/shared/client/i18n.d.ts +30 -0
- package/lib/vendor/shared/client/ui/index.d.ts +214 -0
- package/lib/vendor/shared/dsh-home.d.ts +26 -0
- package/lib/vendor/shared/host-utils.d.ts +82 -0
- package/lib/vendor/shared/loopback.d.ts +12 -0
- package/lib/vendor/shared/package.json +12 -0
- package/lib/vendor/shared/paths.d.ts +35 -0
- package/lib/vendor/shared/settings-namespace.d.ts +82 -0
- package/lib/vendor/shared/sse-hub.d.ts +75 -0
- package/lib/vendor/shared/upgrade-chain.d.ts +140 -0
- package/lib/vendor/shared/upgrade-tick.d.ts +17 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +171 -4
package/lib/client.js
ADDED
|
@@ -0,0 +1,2568 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// 契约外壳(scripts/build-client.ts 生成):external 依赖(React 等)经 factory 注入的 require 解析
|
|
3
|
+
window.__ModuleLoader__.load({
|
|
4
|
+
id: "@sunshaobei/dsh-mcp-manager",
|
|
5
|
+
factory: function (require) {
|
|
6
|
+
var module = { exports: {} }
|
|
7
|
+
var exports = module.exports
|
|
8
|
+
"use strict";
|
|
9
|
+
"use strict";
|
|
10
|
+
var __create = Object.create;
|
|
11
|
+
var __defProp = Object.defineProperty;
|
|
12
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
13
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
14
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
15
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
16
|
+
var __export = (target, all) => {
|
|
17
|
+
for (var name in all)
|
|
18
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
19
|
+
};
|
|
20
|
+
var __copyProps = (to, from, except, desc) => {
|
|
21
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
22
|
+
for (let key of __getOwnPropNames(from))
|
|
23
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
24
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
25
|
+
}
|
|
26
|
+
return to;
|
|
27
|
+
};
|
|
28
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
29
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
30
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
31
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
32
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
33
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
34
|
+
mod
|
|
35
|
+
));
|
|
36
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
37
|
+
|
|
38
|
+
// src/client/index.ts
|
|
39
|
+
var index_exports = {};
|
|
40
|
+
__export(index_exports, {
|
|
41
|
+
apply: () => apply,
|
|
42
|
+
inject: () => inject
|
|
43
|
+
});
|
|
44
|
+
module.exports = __toCommonJS(index_exports);
|
|
45
|
+
|
|
46
|
+
// src/client/style.css
|
|
47
|
+
var style_default = '/* dsh-mcp-manager — 客户端样式(独立 .css 文件)。\n *\n * 由 build-client 的 .css text-loader 构建期内联进 client.js(产物仍自包含单文件、\n * 零运行时依赖、无独立请求)。前缀 dm-(防与 shell / 其他插件样式冲突)。\n *\n * 浮窗视觉:Apple Liquid Glass(半透明毛玻璃 + 0.5px subtle border)。\n * 固体文字/状态色走 --dsw-alias-* + 浅色回退;玻璃底/光边双主题 rgba\n * (light ≈ white/76,dark ≈ zinc-900/74),并经 color-mix 跟随宿主主题变量。\n * 动效:Apple Fluid Curve cubic-bezier(0.16, 1, 0.3, 1)。\n * 浮窗 origin=胶囊锚点→对角 scale 0.72→1;模态 origin=中心→四周 scale 0.78→1。\n * 可点元素 Active Press Feedback;中英混排系统中文字体栈 + line-height ≥1.5。 */\n\n:root {\n --dm-fluid: cubic-bezier(0.16, 1, 0.3, 1);\n --dm-press-scale: 0.96;\n --dm-glass-blur: 28px;\n --dm-glass-saturate: 220%;\n --dm-glass-bg: rgba(255, 255, 255, 0.76);\n --dm-glass-bg-strong: rgba(255, 255, 255, 0.84);\n --dm-glass-bg-solid: var(--dsw-alias-bg-base, #fdfdfd);\n /* 主题跟随 wash:浅色用墨 8%,深色用白 12%(M1:禁止深色下写死浅色 wash) */\n --dm-wash: color-mix(in srgb, #1c1e26 8%, transparent);\n /* 卡片底:浅色白 42% 霜,深色白 7% 霜(配 zinc-900/74 玻璃底) */\n --dm-card-bg: color-mix(in srgb, #ffffff 42%, transparent);\n /* 行 hover:浅色黑 3%,深色白 7%(深色下黑 3% 近不可见) */\n --dm-row-hover: color-mix(in srgb, #000000 3%, transparent);\n --dm-glass-edge: rgba(20, 30, 50, 0.1);\n --dm-glass-rim: rgba(255, 255, 255, 0.92);\n --dm-glass-shadow:\n inset 0 0.5px 0 rgba(255, 255, 255, 0.95), inset 0 0 0 0.5px rgba(255, 255, 255, 0.45),\n 0 1px 1px rgba(20, 30, 50, 0.04), 0 6px 16px rgba(30, 45, 80, 0.08),\n 0 20px 48px rgba(30, 45, 80, 0.12);\n --dm-glass-spec: linear-gradient(\n 180deg,\n rgba(255, 255, 255, 0.72) 0%,\n rgba(255, 255, 255, 0.22) 22%,\n rgba(255, 255, 255, 0.04) 48%,\n rgba(255, 255, 255, 0) 100%\n );\n --dm-font-ui:\n system-ui, -apple-system, "Segoe UI", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei",\n "Noto Sans SC", sans-serif;\n}\n\n@media (prefers-color-scheme: dark) {\n :root {\n --dm-glass-bg: rgba(24, 24, 27, 0.74);\n --dm-glass-bg-strong: rgba(24, 24, 27, 0.86);\n --dm-wash: color-mix(in srgb, #ffffff 12%, transparent);\n --dm-card-bg: color-mix(in srgb, #ffffff 7%, transparent);\n --dm-row-hover: color-mix(in srgb, #ffffff 7%, transparent);\n --dm-glass-edge: rgba(255, 255, 255, 0.14);\n --dm-glass-rim: rgba(255, 255, 255, 0.12);\n --dm-glass-shadow:\n inset 0 0.5px 0 rgba(255, 255, 255, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.1),\n 0 24px 64px rgba(0, 0, 0, 0.5), 0 4px 16px rgba(0, 0, 0, 0.28);\n --dm-glass-spec: linear-gradient(\n 180deg,\n rgba(255, 255, 255, 0.16) 0%,\n rgba(255, 255, 255, 0.03) 30%,\n rgba(255, 255, 255, 0) 100%\n );\n }\n}\nhtml.dark,\n[data-theme="dark"],\n[data-color-mode="dark"] {\n --dm-glass-bg: rgba(24, 24, 27, 0.74);\n --dm-glass-bg-strong: rgba(24, 24, 27, 0.86);\n --dm-wash: color-mix(in srgb, #ffffff 12%, transparent);\n --dm-card-bg: color-mix(in srgb, #ffffff 7%, transparent);\n --dm-row-hover: color-mix(in srgb, #ffffff 7%, transparent);\n --dm-glass-edge: rgba(255, 255, 255, 0.14);\n --dm-glass-rim: rgba(255, 255, 255, 0.12);\n --dm-glass-shadow:\n inset 0 0.5px 0 rgba(255, 255, 255, 0.08), inset 0 1px 0 rgba(255, 255, 255, 0.1),\n 0 24px 64px rgba(0, 0, 0, 0.5), 0 4px 16px rgba(0, 0, 0, 0.28);\n --dm-glass-spec: linear-gradient(\n 180deg,\n rgba(255, 255, 255, 0.16) 0%,\n rgba(255, 255, 255, 0.03) 30%,\n rgba(255, 255, 255, 0) 100%\n );\n}\n\n@media (prefers-contrast: more) {\n :root {\n --dm-glass-bg: rgba(255, 255, 255, 0.94);\n --dm-glass-bg-strong: #ffffff;\n --dm-glass-edge: rgba(20, 30, 50, 0.22);\n /* 高对比下关闭高光 wash,wash 走纯色 */\n --dm-glass-spec: none;\n --dm-wash: #eceff3;\n --dm-card-bg: #ffffff;\n --dm-row-hover: #eef1f5;\n }\n html.dark,\n [data-theme="dark"],\n [data-color-mode="dark"] {\n --dm-glass-bg: rgba(18, 18, 20, 0.96);\n --dm-glass-bg-strong: #121214;\n --dm-glass-edge: rgba(255, 255, 255, 0.28);\n --dm-glass-spec: none;\n --dm-wash: #2a2d34;\n --dm-card-bg: #17171a;\n --dm-row-hover: #26282f;\n }\n /* 高对比下隐藏装饰性高光层,失败行保留纯色语义底 */\n .dm-glass::before {\n display: none;\n }\n .dm-float-row.dm-float-row--fail,\n .dm-server.dm-server--fail {\n background: color-mix(in srgb, #e0483e 12%, var(--dm-glass-bg-strong));\n }\n}\n\n[data-conversation-scroll] {\n position: relative;\n}\n[data-pane="conversation"] {\n position: relative;\n}\n\n/* 毛玻璃公共层 */\n.dm-glass {\n position: relative;\n color: var(--dsw-alias-label-primary, #1c1e26);\n border: 0.5px solid var(--dm-glass-edge);\n background: var(--dm-glass-bg);\n backdrop-filter: blur(var(--dm-glass-blur)) saturate(var(--dm-glass-saturate));\n -webkit-backdrop-filter: blur(var(--dm-glass-blur)) saturate(var(--dm-glass-saturate));\n box-shadow: var(--dm-glass-shadow);\n font-family: var(--dm-font-ui);\n letter-spacing: 0;\n}\n.dm-glass::before {\n content: "";\n position: absolute;\n inset: 0;\n border-radius: inherit;\n background: var(--dm-glass-spec);\n pointer-events: none;\n}\n.dm-glass > * {\n position: relative;\n z-index: 1;\n}\n@media (prefers-reduced-transparency: reduce) {\n .dm-float,\n .dm-float-panel,\n .dm-card,\n .dm-float-action,\n .dm-float-head button,\n .dm-head button,\n .dm-actions button,\n .dm-form-actions button,\n .dm-server,\n .dm-tab[data-active] {\n background: var(--dm-glass-bg-solid, var(--dsw-alias-bg-base, #fdfdfd));\n backdrop-filter: none;\n -webkit-backdrop-filter: none;\n }\n /* 失败行保留语义底,但以纯色混合(相对实底)代替透明罩染 */\n .dm-float-row.dm-float-row--fail,\n .dm-server.dm-server--fail {\n background: color-mix(\n in srgb,\n #e0483e 8%,\n var(--dm-glass-bg-solid, var(--dsw-alias-bg-base, #fdfdfd))\n );\n }\n /* 装饰性高光层在减透明下隐藏 */\n .dm-glass::before {\n display: none;\n }\n}\n\n/* —— 胶囊(高度契约约 26–30px,供 provider-usage offsetY 避让)—— */\n.dm-float {\n position: absolute;\n top: 44px;\n right: 14px;\n z-index: 10;\n display: flex;\n align-items: center;\n gap: 6px;\n padding: 5px 11px;\n border: 0.5px solid var(--dm-glass-edge);\n border-radius: 99px;\n background: var(--dm-glass-bg);\n color: var(--dsw-alias-label-primary, #1c1e26);\n font-size: 12px;\n line-height: 1.4;\n letter-spacing: 0;\n cursor: pointer;\n font-family: var(--dm-font-ui);\n backdrop-filter: blur(var(--dm-glass-blur)) saturate(var(--dm-glass-saturate));\n -webkit-backdrop-filter: blur(var(--dm-glass-blur)) saturate(var(--dm-glass-saturate));\n box-shadow:\n inset 0 0.5px 0 var(--dm-glass-rim),\n 0 2px 10px rgba(15, 18, 28, 0.08);\n transition:\n transform 160ms var(--dm-fluid),\n box-shadow 160ms var(--dm-fluid);\n transform-origin: center;\n -webkit-tap-highlight-color: transparent;\n}\n/* 触控热区(#128,WCAG 2.2 AA ≥24px;外扩至约 44px) */\n.dm-float::after {\n content: "";\n position: absolute;\n inset: -7px;\n border-radius: 99px;\n}\n.dm-float .dm-dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n display: inline-block;\n flex: none;\n /* 缺省中性灰;renderPill 按健康态写 inline 色 */\n background: var(--dsw-alias-label-tertiary, #9aa1ad);\n}\n.dm-float.dm-float--fail .dm-dot {\n background: var(--dsw-alias-state-error-primary, #e0483e) !important;\n box-shadow: 0 0 0 2px color-mix(in srgb, #e0483e 22%, transparent);\n}\n.dm-float .dm-float-fail {\n display: none;\n font-size: 10px;\n font-weight: 700;\n color: var(--dsw-alias-state-error-primary, #e0483e);\n line-height: 1;\n}\n.dm-float.dm-float--fail .dm-float-fail {\n display: inline;\n}\n.dm-float:active {\n transform: scale(var(--dm-press-scale));\n transition-duration: 80ms;\n}\n.dm-float.dm-float--open {\n box-shadow:\n inset 0 0.5px 0 var(--dm-glass-rim),\n 0 0 0 1.5px color-mix(in srgb, #2f7bf6 35%, transparent),\n 0 2px 10px rgba(15, 18, 28, 0.08);\n}\n.dm-float:focus-visible {\n outline: 2px solid var(--dsw-alias-state-info-primary, #3b82f6);\n outline-offset: 2px;\n}\n\n/* —— 浮窗下拉面板:锚点(胶囊)→ 对角展开 —— */\n@keyframes dm-float-in {\n from {\n opacity: 0;\n transform: scale(0.72);\n filter: blur(10px);\n }\n to {\n opacity: 1;\n transform: scale(1);\n filter: blur(0px);\n }\n}\n@keyframes dm-float-out {\n from {\n opacity: 1;\n transform: scale(1);\n filter: blur(0px);\n }\n to {\n opacity: 0;\n transform: scale(0.78);\n filter: blur(8px);\n }\n}\n@keyframes dm-row-in {\n from {\n opacity: 0;\n transform: translateY(6px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n.dm-float-panel {\n position: fixed;\n top: 44px;\n right: 14px;\n width: min(400px, calc(100vw - 36px));\n max-height: min(70vh, 560px);\n max-height: min(70dvh, 560px);\n overflow: auto;\n overscroll-behavior: contain;\n color: var(--dsw-alias-label-primary, #1c1e26);\n border: 0.5px solid var(--dm-glass-edge);\n border-radius: 20px;\n background: var(--dm-glass-bg);\n z-index: 10;\n padding: 14px 14px 12px;\n font-family: var(--dm-font-ui);\n font-size: 12px;\n line-height: 1.55;\n letter-spacing: 0;\n backdrop-filter: blur(var(--dm-glass-blur)) saturate(var(--dm-glass-saturate));\n -webkit-backdrop-filter: blur(var(--dm-glass-blur)) saturate(var(--dm-glass-saturate));\n box-shadow: var(--dm-glass-shadow);\n opacity: 0;\n transform: scale(0.72);\n filter: blur(10px);\n visibility: hidden;\n pointer-events: none;\n /* 缺省 top-right 锚点;placePanel 按四角改写 inline transform-origin */\n transform-origin: top right;\n will-change: opacity, transform, filter;\n}\n.dm-float-panel.dm-float-panel--open {\n visibility: visible;\n pointer-events: auto;\n opacity: 1;\n transform: scale(1);\n filter: blur(0);\n animation: dm-float-in 480ms var(--dm-fluid) both;\n}\n.dm-float-panel.dm-float-panel--closing {\n visibility: visible;\n pointer-events: none;\n animation: dm-float-out 300ms var(--dm-fluid) both;\n}\n.dm-float-panel.dm-float-panel--open .dm-float-row.dm-stagger {\n animation: dm-row-in 320ms var(--dm-fluid) both;\n}\n.dm-float-panel[hidden] {\n display: none !important;\n}\n\n/* #128 移动端档位 */\n.dm-float-panel[data-dm-bp="narrow"] {\n width: calc(100vw - 16px);\n max-height: calc(85vh - 88px);\n max-height: calc(85dvh - 88px);\n padding: 12px 10px 10px;\n border-radius: 14px;\n}\n.dm-float-panel[data-dm-bp="tablet"] {\n width: min(400px, calc(100vw - 24px));\n}\n.dm-float-panel[data-dm-bp="narrow"] .dm-float-row {\n flex-wrap: wrap;\n row-gap: 2px;\n padding: 10px 8px;\n}\n.dm-float-panel[data-dm-bp="narrow"] .dm-float-row .dm-float-meta {\n flex-basis: 100%;\n white-space: normal;\n line-height: 1.5;\n}\n.dm-float-panel[data-dm-bp="narrow"] .dm-float-actions {\n flex: 1;\n justify-content: flex-end;\n}\n.dm-float-action {\n position: relative;\n}\n.dm-float-panel[data-dm-bp="narrow"] .dm-float-action {\n min-height: 36px;\n padding: 6px 14px;\n font-size: 12.5px;\n}\n.dm-float-panel[data-dm-bp="narrow"] .dm-float-action::before {\n content: "";\n position: absolute;\n inset: -4px;\n}\n.dm-float-panel[data-dm-bp="tablet"] .dm-float-action {\n min-height: 30px;\n padding: 4px 11px;\n}\n.dm-float-panel[data-dm-bp="narrow"] .dm-float-head button {\n min-height: 36px;\n padding: 5px 12px;\n font-size: 12.5px;\n}\n\n.dm-float-head {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n margin-bottom: 10px;\n padding: 2px 4px 0;\n}\n.dm-float-head-text {\n flex: 1;\n min-width: 0;\n}\n.dm-float-title {\n font-weight: 600;\n font-size: 13px;\n line-height: 1.4;\n letter-spacing: 0;\n color: var(--dsw-alias-label-primary, #1c1e26);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n.dm-float-health {\n margin-top: 3px;\n font-size: 11px;\n line-height: 1.45;\n color: var(--dsw-alias-label-secondary, #6b7280);\n font-variant-numeric: tabular-nums;\n}\n.dm-float-health .dm-health-ok {\n color: var(--dsw-alias-state-success-primary, #0f9d6e);\n}\n.dm-float-health .dm-health-bad {\n color: var(--dsw-alias-state-error-primary, #e0483e);\n font-weight: 600;\n}\n.dm-float-head button {\n flex: none;\n border: 0.5px solid var(--dm-glass-edge);\n background: color-mix(in srgb, var(--dsw-alias-bg-base, #fff) 55%, transparent);\n color: var(--dsw-alias-label-primary, #1c1e26);\n border-radius: 99px;\n min-height: 28px;\n padding: 4px 11px;\n font-size: 11px;\n line-height: 1.4;\n font-family: inherit;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition:\n transform 160ms var(--dm-fluid),\n background-color 160ms var(--dm-fluid),\n opacity 160ms var(--dm-fluid);\n}\n.dm-float-head button:active,\n.dm-float-action:active,\n.dm-head button:active,\n.dm-actions button:active,\n.dm-form-actions button:active,\n.dm-set-save:active {\n transform: scale(var(--dm-press-scale));\n transition-duration: 80ms;\n opacity: 0.88;\n}\n.dm-float-head button:focus-visible,\n.dm-float-action:focus-visible,\n.dm-head button:focus-visible {\n outline: 2px solid var(--dsw-alias-state-info-primary, #3b82f6);\n outline-offset: 2px;\n}\n\n.dm-float-group {\n margin: 0 0 4px;\n padding: 0;\n}\n.dm-float-group + .dm-float-group {\n margin-top: 14px;\n}\n.dm-float-group-title {\n font-size: 11px;\n line-height: 1.4;\n letter-spacing: 0.04em;\n color: var(--dsw-alias-label-secondary, #8a8f9c);\n font-weight: 600;\n margin: 0 0 6px;\n padding: 0 6px;\n}\n.dm-float-group-title.dm-float-group-title--alert {\n color: var(--dsw-alias-state-error-primary, #e0483e);\n}\n\n.dm-float-row {\n display: flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 6px 8px;\n padding: 8px 8px 10px;\n border: none;\n border-radius: 14px;\n margin-bottom: 2px;\n background: transparent;\n line-height: 1.5;\n transition: background-color 160ms var(--dm-fluid);\n}\n.dm-float-row.dm-float-row--fail {\n background: color-mix(in srgb, #e0483e 6%, transparent);\n}\n.dm-float-row .dm-float-name {\n font-weight: 600;\n font-family: ui-monospace, "SF Mono", Consolas, "PingFang SC", monospace;\n font-size: 12px;\n line-height: 1.4;\n letter-spacing: 0;\n max-width: 150px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n color: var(--dsw-alias-label-primary, #1c1e26);\n}\n.dm-float-row .dm-float-meta {\n flex: 1;\n min-width: 0;\n font-size: 11px;\n line-height: 1.5;\n color: var(--dsw-alias-label-secondary, #8a8f9c);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-variant-numeric: tabular-nums;\n}\n.dm-float-actions {\n flex: none;\n display: flex;\n gap: 4px;\n margin-left: auto;\n flex-wrap: wrap;\n}\n.dm-float-action {\n border: 0.5px solid var(--dm-glass-edge);\n background: color-mix(in srgb, var(--dsw-alias-bg-base, #fff) 50%, transparent);\n color: var(--dsw-alias-label-primary, #1c1e26);\n border-radius: 99px;\n min-height: 28px;\n padding: 3px 10px;\n font-size: 11px;\n line-height: 1.4;\n font-family: inherit;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition:\n transform 160ms var(--dm-fluid),\n background-color 160ms var(--dm-fluid),\n opacity 160ms var(--dm-fluid);\n}\n.dm-float-action.dm-primary,\n.dm-actions button.dm-primary,\n.dm-form-actions button.dm-primary {\n font-weight: 600;\n border-color: color-mix(in srgb, #2f7bf6 35%, transparent);\n background: color-mix(in srgb, #2f7bf6 12%, transparent);\n color: var(--dsw-alias-state-business-primary, #2f6fe0);\n}\n.dm-float-action.dm-danger,\n.dm-actions button.dm-danger {\n color: var(--dsw-alias-state-error-primary, #b42318);\n border-color: color-mix(in srgb, #b42318 30%, transparent);\n}\n\n/* —— 模态管理面板:中心 → 四周 —— */\n@keyframes dm-modal-in {\n from {\n opacity: 0;\n transform: scale(0.78);\n filter: blur(12px);\n }\n to {\n opacity: 1;\n transform: scale(1);\n filter: blur(0px);\n }\n}\n@keyframes dm-modal-out {\n from {\n opacity: 1;\n transform: scale(1);\n filter: blur(0px);\n }\n to {\n opacity: 0;\n transform: scale(0.82);\n filter: blur(10px);\n }\n}\n@keyframes dm-overlay-in {\n from {\n opacity: 0;\n }\n to {\n opacity: 1;\n }\n}\n@keyframes dm-overlay-out {\n from {\n opacity: 1;\n }\n to {\n opacity: 0;\n }\n}\n.dm-overlay {\n position: fixed;\n inset: 0;\n background: var(--dsw-alias-bg-mask-2, rgba(8, 10, 16, 0.28));\n display: flex;\n align-items: center;\n justify-content: center;\n z-index: 9999;\n font-family: var(--dm-font-ui);\n opacity: 0;\n visibility: hidden;\n pointer-events: none;\n backdrop-filter: blur(8px) saturate(120%);\n -webkit-backdrop-filter: blur(8px) saturate(120%);\n}\n.dm-overlay.dm-overlay--open {\n opacity: 1;\n visibility: visible;\n pointer-events: auto;\n animation: dm-overlay-in 360ms var(--dm-fluid) both;\n}\n.dm-overlay.dm-overlay--closing {\n visibility: visible;\n pointer-events: none;\n animation: dm-overlay-out 280ms var(--dm-fluid) both;\n}\n.dm-overlay[hidden] {\n display: none !important;\n}\n.dm-card {\n width: min(860px, 94vw);\n max-height: 86vh;\n display: flex;\n flex-direction: column;\n color: var(--dsw-alias-label-primary, #1c1e26);\n border: 0.5px solid var(--dm-glass-edge);\n border-radius: 22px;\n background: var(--dm-glass-bg-strong);\n box-shadow: var(--dm-glass-shadow);\n backdrop-filter: blur(var(--dm-glass-blur)) saturate(var(--dm-glass-saturate));\n -webkit-backdrop-filter: blur(var(--dm-glass-blur)) saturate(var(--dm-glass-saturate));\n overflow: hidden;\n opacity: 0;\n transform: scale(0.78);\n filter: blur(12px);\n transform-origin: center center;\n will-change: opacity, transform, filter;\n}\n.dm-overlay.dm-overlay--open .dm-card {\n opacity: 1;\n transform: scale(1);\n filter: blur(0);\n transform-origin: center center;\n animation: dm-modal-in 520ms var(--dm-fluid) both;\n}\n.dm-overlay.dm-overlay--closing .dm-card {\n transform-origin: center center;\n animation: dm-modal-out 300ms var(--dm-fluid) both;\n}\n.dm-head {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 16px 18px 10px;\n border-bottom: none;\n background: transparent;\n}\n.dm-head h2 {\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n line-height: 1.35;\n}\n.dm-head .dm-counts {\n font-size: 12px;\n color: var(--dsw-alias-label-secondary, #8a8f9c);\n flex: 1;\n line-height: 1.5;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-variant-numeric: tabular-nums;\n}\n.dm-head .dm-counts .dm-health-bad {\n color: var(--dsw-alias-state-error-primary, #e0483e);\n font-weight: 600;\n}\n.dm-head button {\n border: 0.5px solid var(--dm-glass-edge);\n background: color-mix(in srgb, var(--dsw-alias-bg-base, #fff) 55%, transparent);\n color: var(--dsw-alias-label-primary, #3a3f4b);\n border-radius: 99px;\n min-height: 28px;\n padding: 4px 11px;\n font-size: 12px;\n font-family: inherit;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition:\n transform 160ms var(--dm-fluid),\n background-color 160ms var(--dm-fluid);\n}\n.dm-tabs {\n display: flex;\n gap: 4px;\n padding: 0 18px 8px;\n border-bottom: none;\n background: transparent;\n}\n.dm-tab {\n border: none;\n background: transparent;\n color: var(--dsw-alias-label-secondary, #6b7280);\n min-height: 34px;\n padding: 8px 14px;\n font-size: 13px;\n line-height: 1.4;\n font-family: inherit;\n cursor: pointer;\n border-radius: 99px;\n -webkit-tap-highlight-color: transparent;\n transition:\n background 160ms var(--dm-fluid),\n color 160ms var(--dm-fluid),\n transform 160ms var(--dm-fluid);\n}\n.dm-tab:active {\n transform: scale(var(--dm-press-scale));\n transition-duration: 80ms;\n}\n.dm-tab[data-active] {\n color: var(--dsw-alias-label-primary, #1c1e26);\n font-weight: 600;\n background: var(--dm-wash);\n box-shadow: 0 0 0 0.5px var(--dm-glass-edge);\n}\n.dm-body {\n padding: 4px 18px 18px;\n overflow: auto;\n min-height: 200px;\n overscroll-behavior: contain;\n}\n.dm-status {\n padding: 28px 16px;\n color: var(--dsw-alias-label-secondary, #6b7280);\n font-size: 13px;\n text-align: center;\n line-height: 1.6;\n}\n\n.dm-group {\n margin-bottom: 8px;\n}\n.dm-group > h3 {\n position: sticky;\n top: 0;\n z-index: 1;\n margin: 8px 0 6px;\n font-size: 12px;\n font-weight: 600;\n letter-spacing: 0.04em;\n color: var(--dsw-alias-label-secondary, #525866);\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 8px 0 6px;\n line-height: 1.4;\n background: linear-gradient(\n color-mix(in srgb, var(--dsw-alias-bg-base, #fdfdfd) 88%, transparent) 70%,\n transparent\n );\n}\n.dm-group > h3.dm-group-alert {\n color: var(--dsw-alias-state-error-primary, #e0483e);\n}\n.dm-dot {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n display: inline-block;\n}\n.dm-group > .dm-hint {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary, #8a8f9c);\n margin: 0 0 8px;\n line-height: 1.5;\n}\n.dm-count {\n font-weight: 400;\n color: var(--dsw-alias-label-secondary, #8a8f9c);\n margin-left: 2px;\n font-size: 11px;\n}\n.dm-server {\n border: 0.5px solid var(--dm-glass-edge);\n border-radius: 16px;\n padding: 12px 14px;\n margin-bottom: 10px;\n background: var(--dm-card-bg);\n line-height: 1.5;\n}\n.dm-server.dm-server--fail {\n border-color: color-mix(in srgb, #e0483e 28%, var(--dm-glass-edge));\n background: color-mix(in srgb, #e0483e 5%, transparent);\n}\n.dm-server header {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-wrap: wrap;\n}\n.dm-server .dm-name {\n font-weight: 600;\n font-size: 13px;\n color: var(--dsw-alias-label-primary, #111827);\n font-family: ui-monospace, Consolas, "PingFang SC", monospace;\n overflow-wrap: anywhere;\n word-break: break-word;\n line-height: 1.4;\n}\n.dm-badge {\n font-size: 10px;\n line-height: 1.3;\n padding: 1px 7px;\n border-radius: 99px;\n background: var(--dsw-alias-bg-layer-2, #f1f2f5);\n color: var(--dsw-alias-label-secondary, #525866);\n border: 1px solid var(--dsw-alias-border-l2, #e2e4ea);\n}\n.dm-badge.dm-http {\n background: var(--dsw-alias-state-business-secondary, #eef2ff);\n color: var(--dsw-alias-state-business-primary, #4353a3);\n border-color: var(--dsw-alias-state-business-tertiary, #dde3f8);\n}\n.dm-badge.dm-stdio {\n background: var(--dsw-alias-bg-layer-2, #f5f0ff);\n color: var(--dsw-alias-label-secondary, #6d4bb8);\n border-color: var(--dsw-alias-border-l2, #e6dcf8);\n}\n.dm-badge.dm-st-connected {\n /* 绿底白字:运行中是唯一「正向强状态」,用实底与未连接/失败的中性、红底拉开层级 */\n background: #10b981;\n color: #ffffff;\n border-color: #10b981;\n font-weight: 600;\n}\n.dm-badge.dm-st-connecting,\n.dm-badge.dm-st-reconnecting {\n background: var(--dsw-alias-state-business-secondary, #eff6ff);\n color: var(--dsw-alias-state-business-primary, #1d66b8);\n border-color: var(--dsw-alias-state-business-tertiary, #d3e5fb);\n}\n.dm-badge.dm-st-failed {\n /* 红底白字:失败与运行中同为强状态,实底保证可读性 */\n background: #dc2626;\n color: #ffffff;\n border-color: #dc2626;\n font-weight: 600;\n}\n.dm-badge.dm-st-disabled,\n.dm-badge.dm-st-stopped {\n background: var(--dsw-alias-bg-layer-2, #f1f2f5);\n color: var(--dsw-alias-label-secondary, #6b7280);\n border-color: var(--dsw-alias-border-l2, #e2e4ea);\n}\n.dm-server .dm-endpoint {\n margin: 6px 0 0;\n font-size: 11px;\n color: var(--dsw-alias-label-secondary, #8a8f9c);\n font-family: ui-monospace, Consolas, "PingFang SC", monospace;\n word-break: break-all;\n line-height: 1.5;\n}\n.dm-server .dm-err {\n margin: 6px 0 0;\n font-size: 11px;\n color: var(--dsw-alias-state-error-primary, #b42318);\n line-height: 1.5;\n}\n.dm-server .dm-tools {\n margin: 8px 0 0;\n}\n.dm-server .dm-tools summary {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary, #525866);\n cursor: pointer;\n user-select: none;\n line-height: 1.5;\n}\n.dm-server .dm-tools ul {\n margin: 4px 0 0;\n padding-left: 16px;\n font-size: 11px;\n color: var(--dsw-alias-label-primary, #3a3f4b);\n font-family: ui-monospace, Consolas, "PingFang SC", monospace;\n line-height: 1.55;\n}\n.dm-actions {\n display: flex;\n gap: 6px;\n margin-top: 10px;\n flex-wrap: wrap;\n}\n.dm-actions button {\n border: 0.5px solid var(--dm-glass-edge);\n background: color-mix(in srgb, var(--dsw-alias-bg-base, #fff) 55%, transparent);\n color: var(--dsw-alias-label-primary, #3a3f4b);\n border-radius: 99px;\n min-height: 28px;\n padding: 3px 10px;\n font-size: 11px;\n font-family: inherit;\n line-height: 1.4;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition:\n transform 160ms var(--dm-fluid),\n background-color 160ms var(--dm-fluid);\n}\n.dm-actions button.dm-danger {\n color: var(--dsw-alias-state-error-primary, #b42318);\n border-color: var(--dsw-alias-state-error-tertiary, #f0c4c1);\n}\n.dm-actions button.dm-primary {\n color: var(--dsw-alias-state-business-primary, #1d4ed8);\n border-color: var(--dsw-alias-state-business-tertiary, #bcd1fb);\n background: var(--dsw-alias-state-business-secondary, #f5f8ff);\n font-weight: 600;\n}\n.dm-actions button:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.dm-section {\n margin-bottom: 18px;\n}\n.dm-section > h3 {\n margin: 0 0 8px;\n font-size: 13px;\n font-weight: 600;\n color: var(--dsw-alias-label-primary, #2f3542);\n line-height: 1.4;\n}\n.dm-form {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 10px 12px;\n max-width: 640px;\n}\n.dm-form .dm-full {\n grid-column: 1/-1;\n}\n.dm-field {\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n.dm-field label {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary, #525866);\n font-weight: 600;\n line-height: 1.4;\n}\n.dm-field input,\n.dm-field select,\n.dm-field textarea {\n border: 0.5px solid var(--dm-glass-edge);\n border-radius: 10px;\n padding: 8px 10px;\n min-height: 36px;\n font-size: 12px;\n color: var(--dsw-alias-label-primary, #1c1e26);\n background: color-mix(in srgb, #ffffff 55%, transparent);\n font-family: ui-monospace, Consolas, "PingFang SC", monospace;\n line-height: 1.45;\n}\n.dm-field textarea {\n min-height: 64px;\n resize: vertical;\n line-height: 1.55;\n}\n.dm-field input[type="checkbox"] {\n width: auto;\n min-height: 0;\n}\n.dm-check {\n flex-direction: row;\n align-items: center;\n gap: 8px;\n}\n.dm-check label {\n margin: 0;\n}\n.dm-form-actions {\n grid-column: 1/-1;\n display: flex;\n gap: 8px;\n}\n.dm-form-actions button {\n border: 0.5px solid var(--dm-glass-edge);\n background: color-mix(in srgb, var(--dsw-alias-bg-base, #fff) 55%, transparent);\n color: var(--dsw-alias-label-primary, #3a3f4b);\n border-radius: 99px;\n min-height: 32px;\n padding: 6px 14px;\n font-size: 12px;\n font-family: inherit;\n cursor: pointer;\n -webkit-tap-highlight-color: transparent;\n transition:\n transform 160ms var(--dm-fluid),\n background-color 160ms var(--dm-fluid);\n}\n.dm-form-actions button.dm-primary {\n background: var(--dsw-alias-button-primary-fill, #4f6ef7);\n color: var(--dsw-alias-label-primary-foreground, #fff);\n border-color: var(--dsw-alias-button-primary-fill, #4f6ef7);\n font-weight: 600;\n}\n.dm-hint {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary, #8a8f9c);\n line-height: 1.5;\n}\n.dm-paste-box {\n border: 0.5px solid var(--dm-glass-edge);\n border-radius: 16px;\n padding: 12px;\n background: color-mix(in srgb, #ffffff 40%, transparent);\n max-width: 640px;\n}\n.dm-result {\n font-size: 12px;\n color: var(--dsw-alias-state-success-primary, #0f7a50);\n margin-top: 8px;\n line-height: 1.6;\n}\n.dm-result .dm-skip {\n color: var(--dsw-alias-state-warn-primary, #b45309);\n}\n\n/* 设置页插件卡(settings.plugin.item)——视觉锚点测试 #219 不变 */\n.dm-set-card {\n list-style: none;\n margin: 0;\n padding: 0;\n border: 1px solid var(--dsw-alias-border-l2, #e2e5ea);\n border-radius: 12px;\n background: var(--dsw-alias-bg-layer-3, #fbfbfc);\n overflow: hidden;\n transition:\n border-color 0.16s,\n background 0.16s;\n}\n.dm-set-head {\n width: 100%;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 14px 16px;\n background: transparent;\n border: none;\n cursor: pointer;\n text-align: left;\n color: var(--dsw-alias-label-primary, #1f2329);\n}\n.dm-set-headText {\n display: flex;\n flex-direction: column;\n gap: 4px;\n flex: 1;\n min-width: 0;\n}\n.dm-set-name {\n display: block;\n font-size: 15px;\n font-weight: 600;\n line-height: 1.4;\n color: var(--dsw-alias-label-primary, #1f2329);\n}\n.dm-set-description {\n display: block;\n font-size: 13px;\n line-height: 1.5;\n color: var(--dsw-alias-label-tertiary, #8a919c);\n}\n.dm-set-chevron {\n flex: none;\n color: var(--dsw-alias-label-tertiary, #5f6672);\n transition: transform 0.16s ease;\n}\n.dm-set-chevronOpen {\n transform: rotate(180deg);\n}\n.dm-set-body {\n padding: 4px 14px 14px;\n border-top: 1px solid var(--dsw-alias-border-l1, #e2e5ea);\n display: flex;\n flex-direction: column;\n gap: 9px;\n}\n.dm-set-row {\n display: flex;\n align-items: center;\n gap: 12px;\n margin: 0;\n flex-wrap: wrap;\n}\n.dm-set-row > label {\n font-size: 12.5px;\n color: var(--dsw-alias-label-secondary, #5f6672);\n font-weight: 600;\n white-space: nowrap;\n}\n.dm-set-field {\n display: inline-flex;\n flex-direction: column;\n gap: 3px;\n font-size: 11.5px;\n color: var(--dsw-alias-label-secondary, #5f6672);\n font-weight: 600;\n}\n.dm-set-input {\n min-width: 88px;\n padding: 5px 8px;\n border: 1px solid var(--dsw-alias-border-l1, #e2e5ea);\n border-radius: 6px;\n font-size: 12.5px;\n color: var(--dsw-alias-label-primary, #1f2329);\n background: var(--dsw-alias-bg-layer-1, #fff);\n box-sizing: border-box;\n}\n.dm-set-input:focus {\n outline: none;\n border-color: var(--dsw-alias-state-info-primary, #3b82f6);\n}\n.dm-set-hint {\n margin-top: 0;\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary, #8a919c);\n line-height: 1.5;\n}\n.dm-set-foot {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: 10px;\n margin-top: 10px;\n}\n.dm-set-foot .dm-set-saved {\n font-size: 12px;\n color: var(--dsw-alias-state-success-primary, #16a34a);\n}\n.dm-set-foot .dm-set-error {\n font-size: 12px;\n color: var(--dsw-alias-state-danger-primary, #dc2626);\n}\n.dm-set-save {\n border: 1px solid var(--dsw-alias-border-l1, #e2e5ea);\n background: var(--dsw-alias-bg-base, #fff);\n color: var(--dsw-alias-label-primary, #3a3f4b);\n border-radius: 6px;\n padding: 5px 14px;\n font-size: 12px;\n cursor: pointer;\n transition:\n transform 160ms var(--dm-fluid),\n background-color 160ms var(--dm-fluid);\n}\n.dm-set-save:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n/* #128 悬停态统一包裹 @media(hover:hover) */\n@media (hover: hover) {\n .dm-float:hover {\n background: color-mix(in srgb, var(--dsw-alias-interactive-bg-hover, #f2f4f8) 70%, transparent);\n }\n .dm-float-head button:hover {\n background: var(--dsw-alias-interactive-bg-hover, #eef1f5);\n }\n .dm-float-action:hover {\n background: var(--dsw-alias-interactive-bg-hover, #eef1f5);\n }\n .dm-float-row:hover {\n background: var(--dm-row-hover);\n }\n .dm-head button:hover {\n background: var(--dsw-alias-interactive-bg-hover, #eef1f5);\n }\n .dm-actions button:hover {\n background: var(--dsw-alias-interactive-bg-hover, #eef1f5);\n }\n .dm-form-actions button:hover {\n background: var(--dsw-alias-interactive-bg-hover, #eef1f5);\n }\n .dm-set-card:hover {\n border-color: var(--dsw-alias-label-dimmed, #9ba1a6);\n }\n .dm-set-head:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.04));\n }\n .dm-set-save:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.06));\n }\n}\n\n/* #362 工具级禁用 checkbox(#401 勾选=禁用,红色 ×) */\n.dm-float-tools {\n margin: 4px 0 0 16px;\n width: 100%;\n}\n.dm-float-tools summary {\n font-size: 11px;\n color: var(--dsw-alias-label-secondary, #525866);\n cursor: pointer;\n user-select: none;\n line-height: 1.5;\n}\n.dm-float-tool-list {\n display: flex;\n flex-direction: column;\n gap: 2px;\n margin: 4px 0 0;\n max-height: 140px;\n overflow: auto;\n}\n.dm-float-tool,\n.dm-tool {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 11px;\n color: var(--dsw-alias-label-primary, #3a3f4b);\n font-family: ui-monospace, Consolas, monospace;\n cursor: pointer;\n line-height: 1.6;\n overflow-wrap: anywhere;\n word-break: break-word;\n min-width: 0;\n max-width: 100%;\n}\n.dm-float-tool input,\n.dm-tool input {\n width: 13px;\n height: 13px;\n margin: 0;\n flex: none;\n appearance: none;\n -webkit-appearance: none;\n border: 1px solid var(--dsw-alias-border-l2, #c3c8d2);\n border-radius: 3px;\n background: var(--dsw-alias-bg-base, #fff);\n cursor: pointer;\n position: relative;\n box-sizing: border-box;\n}\n.dm-float-tool input:checked,\n.dm-tool input:checked {\n border-color: var(--dsw-alias-state-error-primary, #b42318);\n background: var(--dsw-alias-state-error-secondary, #fef2f2);\n}\n.dm-float-tool input:checked::after,\n.dm-tool input:checked::after {\n content: "";\n position: absolute;\n left: 50%;\n top: 50%;\n width: 8px;\n height: 8px;\n transform: translate(-50%, -50%);\n background:\n linear-gradient(\n 45deg,\n transparent 43%,\n var(--dsw-alias-state-error-primary, #b42318) 43%,\n var(--dsw-alias-state-error-primary, #b42318) 57%,\n transparent 57%\n ),\n linear-gradient(\n -45deg,\n transparent 43%,\n var(--dsw-alias-state-error-primary, #b42318) 43%,\n var(--dsw-alias-state-error-primary, #b42318) 57%,\n transparent 57%\n );\n}\n.dm-float-tool input:hover,\n.dm-tool input:hover {\n border-color: var(--dsw-alias-state-info-primary, #3b82f6);\n}\n.dm-float-tool input:checked:hover,\n.dm-tool input:checked:hover {\n border-color: var(--dsw-alias-state-error-primary, #b42318);\n}\n.dm-float-tool input:focus-visible,\n.dm-tool input:focus-visible {\n outline: 2px solid var(--dsw-alias-state-info-primary, #3b82f6);\n outline-offset: 1px;\n}\n.dm-float-tool:has(input:checked),\n.dm-tool:has(input:checked) {\n color: var(--dsw-alias-state-error-primary, #b42318);\n}\n.dm-float-hint {\n font-size: 10.5px;\n color: var(--dsw-alias-label-tertiary, #8a919c);\n line-height: 1.5;\n padding: 4px 8px 2px;\n}\n.dm-subgroup {\n margin: 6px 0 10px;\n}\n.dm-subgroup-title {\n margin: 0 0 4px;\n font-size: 11px;\n font-weight: 600;\n color: var(--dsw-alias-label-secondary, #525866);\n line-height: 1.4;\n}\n.dm-group > h3 .dm-count {\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary, #8a919c);\n font-weight: 600;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .dm-float,\n .dm-float-panel,\n .dm-float-panel.dm-float-panel--open,\n .dm-float-panel.dm-float-panel--closing,\n .dm-float-panel.dm-float-panel--open .dm-float-row.dm-stagger,\n .dm-overlay,\n .dm-overlay.dm-overlay--open,\n .dm-overlay.dm-overlay--closing,\n .dm-card,\n .dm-overlay.dm-overlay--open .dm-card,\n .dm-overlay.dm-overlay--closing .dm-card,\n .dm-float-action,\n .dm-float-head button,\n .dm-head button,\n .dm-actions button,\n .dm-form-actions button,\n .dm-tab {\n animation: none !important;\n transition-duration: 1ms !important;\n }\n .dm-float-panel.dm-float-panel--open,\n .dm-overlay.dm-overlay--open .dm-card {\n transform: none;\n filter: none;\n }\n}\n';
|
|
48
|
+
|
|
49
|
+
// src/vendor/shared/client/ui/ui.css
|
|
50
|
+
var ui_default = '/* ================= T1 五原语层(dsu-*)唯一副本 =================\n * 职责:Surface / Field / SegmentedControl / Status+Badge 的唯一表现面。\n * Button 的视觉本体由**调用点自带的领域类**承担(见块尾说明),本块不出 dsu-btn 规则。\n *\n * 【怎么进到浏览器:ts 侧拼接,不是 @import】\n * 各包 src/client/index.ts 两行接缝:\n * import UI_CSS from "<到仓库根的相对路径>/shared/client/ui/ui.css";\n * ensureStyle({ id: STYLE_ID, cssText: UI_CSS + STYLE, version: CSS_VERSION });\n * **顺序不可颠倒**:原语层在前(当底座),各包 style.css 在后,这样包内针对原语钩子的\n * 窄屏/领域覆盖(同优先级靠后者胜)才压得住——provider-usage 的 @media 480px 段正是\n * 靠这条把 .dsu-badge 的 max-width 放宽到 100%、.dsu-surface-pane 的 padding 收到 10px。\n * 写成 STYLE + UI_CSS 时这两条覆盖会变死规则,build 仍 exit 0、窄屏却悄悄变回原样。\n * build-client 的 .css text-loader(scripts/build/build-client.ts)把本文件当**纯文本**\n * 内联进各包 client.js,产物仍自包含单文件、零运行时依赖、无独立请求。\n *\n * 【为什么不是 @import:R3 实测证伪,勿再提】\n * 试过在各包 style.css 顶部写 @import 指向本文件:text-loader 不解析 CSS,@import 会\n * 原样留在产物字符串里(实测 `grep -c \'@import\' lib/client.js` → 1),而探针规则\n * `grep -c \'dsu-probe\' lib/client.js` → 0;浏览器随后按**页面相对 URL** 去请求那个\n * 路径 → 404 → 规则静默消失,**而 build 仍 exit 0、测试仍全绿**。这是最坏的失败模式。\n *\n * 【与「各包 style.css 单文件」约定的关系】\n * DEVELOPMENT §3 要求样式不写进 ts、留在各包 style.css。本文件不违反它:本文件**不含\n * 任何包的领域样式**,只是跨包共享的那一段原语规则的单一事实源,由 ts 侧拼接注入;\n * 各包 style.css 仍是该包领域样式的唯一位置。\n *\n * 纪律:颜色全走 --dsw-alias-* + 浅色回退(禁裸 hex);悬停一律 @media (hover: hover)\n * 包裹(#128 规格第 7 条),断点机制全仓只有这一套;禁 role="navigation"(语义层在\n * shared/client/ui/index.tsx 冻结,本块只管表现,结构不冻结)。\n * 消费包不必逐档消费:未消费的档位在本包是死规则,属有意接受的冗余——宁可一段没人用的\n * 规则,也不要给原语层开出「按包裁剪规则块」这条会立刻分叉的机制。\n */\n.dsu-surface-pane {\n margin-bottom: 16px;\n padding: 12px;\n border: 1px solid var(--dsw-alias-border-l1, #e2e5ea);\n border-radius: 10px;\n background: var(--dsw-alias-bg-base, #ffffff);\n font-size: 12px;\n line-height: 1.7;\n}\n/* 极窄容器降级(#1015 A+C 的 C-3 配套):宿主设置弹窗在 375px 下不折叠左侧导航,\n 内容列被压到约 139px,再经 pane padding 12px×2 层层扣减后趋势区只剩 56px——\n 实测装 dsh-web-mobile@3.0.3 亦然(nav 仍 flex:0 0 auto 固定 188px),故根因在宿主\n 布局、插件侧无法获得更多宽度。此处能做的唯一有效动作是**把容器自身的 padding 让给内容**:\n 收掉 padding 后可用宽度翻倍(56px → 104px 量级),标题/控件不再逐词竖排。\n 断点判据用 trend.tsx 已有的 data-dou-col(由 ResizeObserver 量**本窗格实测宽**写入),\n 不用视口断点也不依赖 container-type:宿主把内容列压到多窄与视口宽度无关,\n 桌面窄窗同样会命中,这是刻意的。 */\n.dou-trend-body[data-dou-col="s"] .dsu-surface-pane {\n padding: 6px 4px;\n border-radius: 8px;\n}\n/* 极窄档再让出设置区外壳的 padding:实测宿主 375px 下 content 仅 139px,经 set-body\n padding 14 与 pane padding 各扣一轮后,内容实得 34px——不足一行中文。窄档下这些\n padding 是纯损失(外壳描边已提供视觉边界),一并收掉。 */\n/* set-body 是所有设置区窗格共用的外壳,:has() 把作用域限死在「含窄档趋势窗格」的那一个,\n 不影响 usage/report/history/providers 等其他窗格(宿主与本仓均已在用 :has())。 */\n.dou-set-body:has(.dou-trend-body[data-dou-col="s"]) {\n padding: 4px 2px;\n}\n.dsu-surface-card {\n /* 壳宽 560px 是外壳卡独有的约束(原 settings/index.tsx 内联 maxWidth,行为不变收敛进样式表) */\n max-width: 560px;\n border: 1px solid var(--dsw-alias-border-l1, #e2e5ea);\n border-radius: 12px;\n overflow: hidden;\n background: var(--dsw-alias-bg-base, #ffffff);\n box-shadow: 0 1px 3px rgba(15, 23, 42, 0.05);\n}\n\n/* ---- Field:标签 + 控件 ----\n 行内字段 nowrap 横排,换行能力由 .dsu-field-row 的 flex-wrap 承担\n (约 350px 内容宽度下 nowrap 横排必然溢出,四个数字输入行必须能折行)。 */\n.dsu-field {\n margin-right: 12px;\n white-space: nowrap;\n}\n.dsu-field-block {\n /* 标签在上、控件在下:与 .dsu-field(行内标签)并存会让同一行里出现两种排版结构,\n 跨字段换行时左边缘对不齐。此处统一为竖排,行内字段若需保持原样走 .dsu-field。 */\n display: flex;\n flex-direction: column;\n align-items: flex-start;\n gap: 4px;\n margin-bottom: 8px;\n}\n/* 标签文本是 <label> 内的裸文本节点(隐式关联 select),不是子元素——\n 故只能用 font/ ::first-line 类手段无法精确命中。改为给 label 本身定排版,\n 并让 select 独占一行:裸文本节点会生成匿名 flex item,与 select 同行。\n 这里把 label 设为 flex column,裸文本成为它的匿名 item,select 成为第二个。 */\n.dsu-field-block > select {\n margin-top: 4px;\n align-self: flex-start;\n}\n.dsu-field-block {\n font-size: 11px;\n color: var(--dsw-alias-label-tertiary, #6d7480);\n}\n.dsu-field-block > select {\n font-size: 12px;\n color: var(--dsw-alias-label-primary, #1f2329);\n}\n.dsu-field-row {\n display: flex;\n flex-wrap: wrap;\n row-gap: 6px;\n margin-bottom: 8px;\n}\n.dsu-input {\n margin-left: 6px;\n padding: 2px 6px;\n border: 1px solid var(--dsw-alias-border-l2, #e8eaf0);\n border-radius: 4px;\n}\n.dsu-input-number {\n width: 64px;\n}\n\n/* ---- SegmentedControl:bar=顶部导航条 / pill=行内药丸组 / plain=朴素按钮组 ----\n * 窄屏:bar 靠 overflow-x + nowrap 横滑(与收敛前一致),pill/plain 靠 flex-wrap 折行。\n * 激活项由 [data-dsu-seg-active] 定位而非类名,换实现不会打坏「激活档滚进视野」。 */\n.dsu-seg {\n display: flex;\n}\n.dsu-seg-bar {\n gap: 4px;\n padding: 10px 14px;\n border-bottom: 1px solid var(--dsw-alias-border-l1, #e2e5ea);\n background: var(--dsw-alias-bg-layer-1, #f5f6f8);\n overflow-x: auto;\n white-space: nowrap;\n scrollbar-width: none;\n}\n.dsu-seg-bar::-webkit-scrollbar {\n display: none;\n}\n.dsu-seg-bar .dsu-seg-item {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n min-height: 32px;\n padding: 5px 14px;\n flex: none;\n white-space: nowrap;\n border: 1px solid transparent;\n border-radius: 8px;\n background: transparent;\n color: var(--dsw-alias-label-secondary, #5f6672);\n font-size: 12px;\n cursor: pointer;\n font-family: inherit;\n}\n.dsu-seg-bar .dsu-seg-item[data-dsu-seg-active="true"] {\n background: var(--dsw-alias-bg-base, #ffffff);\n border-color: var(--dsw-alias-border-l1, #e2e5ea);\n color: var(--dsw-alias-label-primary, #1f2329);\n font-weight: 600;\n box-shadow: 0 1px 3px rgba(15, 23, 42, 0.08);\n}\n.dsu-seg-bar .dsu-seg-item:focus-visible {\n outline: 2px solid var(--dsw-alias-state-info-primary, #4a7dd6);\n outline-offset: 2px;\n}\n@media (hover: hover) {\n .dsu-seg-bar .dsu-seg-item:hover {\n color: var(--dsw-alias-label-primary, #1f2329);\n }\n}\n.dsu-seg-pill {\n align-items: center;\n gap: 6px;\n flex-wrap: wrap;\n margin-bottom: 10px;\n}\n.dsu-seg-pill .dsu-seg-item {\n border: 1px solid var(--dsw-alias-border-l1, #e2e5ea);\n background: var(--dsw-alias-bg-layer-1, #f5f6f8);\n color: var(--dsw-alias-label-tertiary, #6d7480);\n border-radius: 999px;\n padding: 3px 12px;\n min-height: 28px;\n font-size: 11px;\n cursor: pointer;\n font-family: inherit;\n}\n.dsu-seg-pill .dsu-seg-item[data-dsu-seg-active="true"] {\n background: var(--dsw-alias-state-info-soft, rgba(47, 111, 237, 0.1));\n border-color: var(--dsw-alias-state-info-primary, #4a7dd6);\n color: var(--dsw-alias-state-info-primary, #4a7dd6);\n font-weight: 600;\n}\n.dsu-seg-plain {\n gap: 6px;\n flex-wrap: wrap;\n /* 档内项距 6px、档与档之间靠外层 .dou-trend-controls 的 row-gap 拉开——\n 两级间距必须分层:项距 6 太挤会让「日/周/月」和「7d/30d/90d」读成一串。 */\n margin-bottom: 6px;\n}\n/* 段内项的视觉分组:给「粒度/范围」这类**同类单选**加一个共同的浅底槽,\n 与「口径/指标」这类**独立选择器**在形状上分开——此前四组控件全是同样的\n 透明圆角按钮平铺,视觉上分不出哪几个是一组。槽只描边不填色,\n 保持与设置页底色同调、不与已选态的 brand 底色抢注意力。 */\n.dsu-seg-plain > .dsu-seg-item {\n border: 1px solid var(--dsw-alias-border-l3, #d7dae0);\n background: var(--dsw-alias-bg-layer-1, #f5f6f8);\n color: var(--dsw-alias-label-secondary, #5f6672);\n padding: 3px 12px;\n border-radius: 6px;\n font-size: 12.5px;\n min-height: 26px;\n}\n.dsu-seg-plain > .dsu-seg-item:hover {\n background: var(--dsw-alias-interactive-bg-hover, rgba(0, 0, 0, 0.04));\n}\n.dsu-seg-plain > .dsu-seg-item[data-dsu-seg-active="true"] {\n background: var(--dsw-alias-bg-base, #ffffff);\n color: var(--dsw-alias-label-primary, #1f2329);\n font-weight: 600;\n border-color: var(--dsw-alias-border-l3, #d7dae0);\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.08);\n}\n/* plain 档的项**不出规则**:视觉与触控热区由消费包自己的按钮类承担——\n 本包调用点传 itemClassName="dou-btn",命中 #128 触控热区\n (.dou-panel[data-dou-bp="narrow"] .dou-btn::before 外扩)与既有按钮规则。 */\n\n/* ---- Status:状态点(无 label 时为装饰点,语义由 aria-hidden 承担)---- */\n.dsu-status {\n display: inline-block;\n width: 8px;\n height: 8px;\n border-radius: 50%;\n margin-right: 6px;\n vertical-align: middle;\n flex: none;\n}\n.dsu-status[data-dsu-tone="ok"] {\n background: var(--dsw-alias-state-success-primary, #0f9d6e);\n}\n.dsu-status[data-dsu-tone="warn"] {\n background: var(--dsw-alias-state-warn-primary, #c9820b);\n}\n.dsu-status[data-dsu-tone="err"] {\n background: var(--dsw-alias-state-error-primary, #d64545);\n}\n.dsu-status[data-dsu-tone="off"],\n.dsu-status[data-dsu-tone="neutral"] {\n background: var(--dsw-alias-label-tertiary, #9aa0ab);\n}\n\n/* ---- Badge:状态徽标 ----\n * 色与边由两个自定义属性承载,尺寸档与色调档各改其一,避免 tone×size 组合爆炸。\n * 规则顺序即优先级:size(定中性底)→ tone(定色)→ muted(定弱化,末位覆盖 tone)。\n * sm 档承载 provider 身份药丸(含窄屏 100% 让位,由既有 480px 段改写本选择器)。 */\n.dsu-badge {\n --dsu-badge-fg: inherit;\n --dsu-badge-bd: var(--dsw-alias-border-l1, #d7dae0);\n flex: none;\n line-height: 1.5;\n color: var(--dsu-badge-fg);\n border: 1px solid var(--dsu-badge-bd);\n}\n.dsu-badge[data-dsu-size="sm"] {\n max-width: 55%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n font-size: 11px;\n padding: 0 8px;\n border-radius: 99px;\n background: var(--dsw-alias-bg-layer-1, #f8f9fb);\n}\n.dsu-badge[data-dsu-size="xs"] {\n --dsu-badge-bd: transparent;\n font-size: 10px;\n padding: 1px 6px;\n border-radius: 8px;\n background: transparent;\n}\n.dsu-badge[data-dsu-tone="ok"] {\n --dsu-badge-fg: var(--dsw-alias-state-success-primary, #3f9d63);\n --dsu-badge-bd: var(--dsw-alias-state-success-primary, #3f9d63);\n}\n.dsu-badge[data-dsu-tone="warn"] {\n --dsu-badge-fg: var(--dsw-alias-state-warn-primary, #c9820b);\n --dsu-badge-bd: var(--dsw-alias-state-warn-primary, #c9820b);\n}\n.dsu-badge[data-dsu-tone="err"] {\n --dsu-badge-fg: var(--dsw-alias-state-error-primary, #d64545);\n --dsu-badge-bd: var(--dsw-alias-state-error-primary, #d64545);\n}\n.dsu-badge[data-dsu-tone="off"] {\n --dsu-badge-fg: var(--dsw-alias-label-tertiary, #9aa0ab);\n}\n.dsu-badge[data-dsu-muted="true"] {\n --dsu-badge-fg: var(--dsw-alias-label-tertiary, #9aa0ab);\n border-style: dashed;\n}\n\n/* ---- Button:本块不出规则 ----\n Button 原语只出 dsu-btn 钩子与恒定的 type="button" 语义;视觉与触控热区由**调用点\n 传入的领域类**承担(provider-usage 传 .dou-btn、lan-proxy 传 .lp-set-save……)。\n R1 时 Button 曾硬编码 .dou-btn,但原语层上提到档 C 后,那条类名就成了「provider-usage\n 的领域契约写在跨包层里」——别的包挂上去只会得到一条永远命不中的死类。#128 触控目标\n 契约又是按**类名**选中的(.dou-panel[data-dou-bp="narrow"] .dou-btn::before 的\n inset:-4px 把 narrow 档的 min-height:36px 外扩到约 44px 命中区),所以各包的按钮类\n 必须仍挂在按钮元素上;在这里重复一份 dsu-btn 规则只会制造第二事实源。 */\n';
|
|
51
|
+
|
|
52
|
+
// src/vendor/shared/client/ensure-style.ts
|
|
53
|
+
function ensureStyle(options) {
|
|
54
|
+
const opts = options || {};
|
|
55
|
+
const id = opts.id;
|
|
56
|
+
const cssText = opts.cssText;
|
|
57
|
+
const version = opts.version;
|
|
58
|
+
if (typeof id !== "string" || id === "" || typeof cssText !== "string") {
|
|
59
|
+
throw new TypeError("ensureStyle: { id, cssText } 为必填且须为字符串(id 非空)");
|
|
60
|
+
}
|
|
61
|
+
const dispose = function() {
|
|
62
|
+
const node = document.getElementById(id);
|
|
63
|
+
if (node !== null) node.remove();
|
|
64
|
+
};
|
|
65
|
+
if (document.head == null) return dispose;
|
|
66
|
+
const existing = document.getElementById(id);
|
|
67
|
+
if (existing !== null) {
|
|
68
|
+
if (version === void 0) return dispose;
|
|
69
|
+
if (existing.dataset.version === version) return dispose;
|
|
70
|
+
existing.remove();
|
|
71
|
+
}
|
|
72
|
+
const style = document.createElement("style");
|
|
73
|
+
style.id = id;
|
|
74
|
+
if (version !== void 0) style.dataset.version = version;
|
|
75
|
+
style.textContent = cssText;
|
|
76
|
+
document.head.appendChild(style);
|
|
77
|
+
return dispose;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/client/index.ts
|
|
81
|
+
var React3 = __toESM(require("react"), 1);
|
|
82
|
+
|
|
83
|
+
// src/shared/placement-math.ts
|
|
84
|
+
var DEFAULT_Z_INDEX_BASE = 10;
|
|
85
|
+
var Z_INDEX_BASE_MIN = 1;
|
|
86
|
+
var Z_INDEX_BASE_MAX = 9e3;
|
|
87
|
+
function clampZIndexBase(value, dflt) {
|
|
88
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return dflt;
|
|
89
|
+
return Math.min(Z_INDEX_BASE_MAX, Math.max(Z_INDEX_BASE_MIN, Math.round(value)));
|
|
90
|
+
}
|
|
91
|
+
function panelAnchorForPosition(placement) {
|
|
92
|
+
return placement === "bottom-right" || placement === "bottom-left" ? "bottom" : "top";
|
|
93
|
+
}
|
|
94
|
+
var BREAKPOINT_NARROW_MAX = 480;
|
|
95
|
+
var BREAKPOINT_TABLET_MAX = 834;
|
|
96
|
+
function breakpointForWidth(width) {
|
|
97
|
+
const w = Number.isFinite(width) ? width : Number.POSITIVE_INFINITY;
|
|
98
|
+
if (w <= BREAKPOINT_NARROW_MAX) return "narrow";
|
|
99
|
+
if (w <= BREAKPOINT_TABLET_MAX) return "tablet";
|
|
100
|
+
return "wide";
|
|
101
|
+
}
|
|
102
|
+
function clampPointToViewport(x, y, width, height, viewportW, viewportH, safeInset = 0) {
|
|
103
|
+
const ins = Number.isFinite(safeInset) ? Math.max(0, safeInset) : 0;
|
|
104
|
+
const hiX = Math.max(ins, viewportW - width - ins);
|
|
105
|
+
const hiY = Math.max(ins, viewportH - height - ins);
|
|
106
|
+
return {
|
|
107
|
+
x: Math.min(Math.max(x, ins), hiX),
|
|
108
|
+
y: Math.min(Math.max(y, ins), hiY)
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
function composerDockedAtBottom(seatRect, containerRect) {
|
|
112
|
+
if (seatRect == null || containerRect == null) return false;
|
|
113
|
+
return Math.abs(seatRect.bottom - containerRect.bottom) <= 0.5;
|
|
114
|
+
}
|
|
115
|
+
function bottomAnchorEdge(containerBottom, seatTop, docked) {
|
|
116
|
+
if (docked && typeof seatTop === "number" && Number.isFinite(seatTop)) return seatTop;
|
|
117
|
+
return containerBottom;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// src/shared/status.ts
|
|
121
|
+
var SERVER_STATES = {
|
|
122
|
+
connected: "connected",
|
|
123
|
+
connecting: "connecting",
|
|
124
|
+
reconnecting: "reconnecting",
|
|
125
|
+
disabled: "disabled",
|
|
126
|
+
stopped: "stopped",
|
|
127
|
+
failed: "failed"
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// src/shared/frames.ts
|
|
131
|
+
var SSE_FRAMES = {
|
|
132
|
+
summary: "summary",
|
|
133
|
+
uiConfigChanged: "ui-config-changed",
|
|
134
|
+
ping: "ping"
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
// src/shared/routes.ts
|
|
138
|
+
var ROUTES = {
|
|
139
|
+
servers: "/api/dsh-mcp/servers",
|
|
140
|
+
config: "/api/dsh-mcp/config",
|
|
141
|
+
session: "/api/dsh-mcp/session",
|
|
142
|
+
resume: "/api/dsh-mcp/resume",
|
|
143
|
+
connect: "/api/dsh-mcp/servers/connect",
|
|
144
|
+
disconnect: "/api/dsh-mcp/servers/disconnect",
|
|
145
|
+
reconnect: "/api/dsh-mcp/servers/reconnect",
|
|
146
|
+
importJson: "/api/dsh-mcp/import/json",
|
|
147
|
+
sources: "/api/dsh-mcp/sources",
|
|
148
|
+
sourceSave: "/api/dsh-mcp/sources/save",
|
|
149
|
+
sourceActivate: "/api/dsh-mcp/sources/activate",
|
|
150
|
+
events: "/api/dsh-mcp/events",
|
|
151
|
+
health: "/api/dsh-mcp/health",
|
|
152
|
+
toolDisable: "/api/dsh-mcp/tool-disable"
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// src/shared/constants.ts
|
|
156
|
+
var MCP_MANAGER_BUNDLE_PACKAGE = "dsh-mcp-manager";
|
|
157
|
+
var MCP_MANAGER_ROW_ID = "dsh-mcp-manager";
|
|
158
|
+
var MCP_MANAGER_SETTINGS_NAMESPACE = "dsh-mcp-manager";
|
|
159
|
+
var MCP_MANAGER_IDENTITY = Object.freeze({
|
|
160
|
+
bundlePackage: MCP_MANAGER_BUNDLE_PACKAGE,
|
|
161
|
+
rowId: MCP_MANAGER_ROW_ID,
|
|
162
|
+
settingsNamespace: MCP_MANAGER_SETTINGS_NAMESPACE,
|
|
163
|
+
rowConfigKey: `${MCP_MANAGER_BUNDLE_PACKAGE}#${MCP_MANAGER_ROW_ID}`
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// src/client/core/constants.ts
|
|
167
|
+
var API = {
|
|
168
|
+
servers: ROUTES.servers,
|
|
169
|
+
connect: ROUTES.connect,
|
|
170
|
+
disconnect: ROUTES.disconnect,
|
|
171
|
+
reconnect: ROUTES.reconnect,
|
|
172
|
+
importJson: ROUTES.importJson,
|
|
173
|
+
sources: ROUTES.sources,
|
|
174
|
+
sourceSave: ROUTES.sourceSave,
|
|
175
|
+
sourceActivate: ROUTES.sourceActivate,
|
|
176
|
+
session: ROUTES.session,
|
|
177
|
+
resume: ROUTES.resume,
|
|
178
|
+
config: ROUTES.config,
|
|
179
|
+
events: ROUTES.events,
|
|
180
|
+
health: ROUTES.health,
|
|
181
|
+
toolDisable: ROUTES.toolDisable
|
|
182
|
+
};
|
|
183
|
+
var STATUS_ORDER = [
|
|
184
|
+
{
|
|
185
|
+
key: SERVER_STATES.connected,
|
|
186
|
+
titleKey: "stConnected",
|
|
187
|
+
dot: "var(--dsw-alias-state-success-primary,#0f9d6e)"
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
key: SERVER_STATES.connecting,
|
|
191
|
+
titleKey: "stConnecting",
|
|
192
|
+
dot: "var(--dsw-alias-state-business-primary,#2f7bf6)"
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
key: SERVER_STATES.reconnecting,
|
|
196
|
+
titleKey: "stReconnecting",
|
|
197
|
+
dot: "var(--dsw-alias-state-warn-primary,#e08b1e)"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
key: SERVER_STATES.stopped,
|
|
201
|
+
titleKey: "stStopped",
|
|
202
|
+
dot: "var(--dsw-alias-label-tertiary,#9aa1ad)"
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
key: SERVER_STATES.disabled,
|
|
206
|
+
titleKey: "stDisabled",
|
|
207
|
+
dot: "var(--dsw-alias-label-tertiary,#9aa1ad)"
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
key: SERVER_STATES.failed,
|
|
211
|
+
titleKey: "stFailed",
|
|
212
|
+
dot: "var(--dsw-alias-state-error-primary,#e0483e)"
|
|
213
|
+
}
|
|
214
|
+
];
|
|
215
|
+
var STATUS_TEXT = {
|
|
216
|
+
[SERVER_STATES.connected]: "stConnected",
|
|
217
|
+
[SERVER_STATES.connecting]: "stConnecting",
|
|
218
|
+
[SERVER_STATES.reconnecting]: "stReconnecting",
|
|
219
|
+
[SERVER_STATES.stopped]: "stStopped",
|
|
220
|
+
[SERVER_STATES.disabled]: "stDisabled",
|
|
221
|
+
[SERVER_STATES.failed]: "stFailed"
|
|
222
|
+
};
|
|
223
|
+
function statusDot(status) {
|
|
224
|
+
const group = STATUS_ORDER.find((entry) => entry.key === status);
|
|
225
|
+
return group !== void 0 ? group.dot : "var(--dsw-alias-label-tertiary,#9aa1ad)";
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// src/client/core/state.ts
|
|
229
|
+
function createState() {
|
|
230
|
+
return {
|
|
231
|
+
overlay: void 0,
|
|
232
|
+
card: void 0,
|
|
233
|
+
bodyEl: void 0,
|
|
234
|
+
open: false,
|
|
235
|
+
panelOpener: void 0,
|
|
236
|
+
activeTab: "servers",
|
|
237
|
+
servers: [],
|
|
238
|
+
counts: {},
|
|
239
|
+
editingName: void 0,
|
|
240
|
+
editing: void 0,
|
|
241
|
+
floatPill: void 0,
|
|
242
|
+
floatPanel: void 0,
|
|
243
|
+
floatOpen: false,
|
|
244
|
+
currentCwd: void 0,
|
|
245
|
+
sessionResolved: false,
|
|
246
|
+
warnedUnknownSession: false,
|
|
247
|
+
unknownSessionFrames: 0,
|
|
248
|
+
projectRoot: void 0,
|
|
249
|
+
updateFloatState: void 0,
|
|
250
|
+
mcpUiConfig: {
|
|
251
|
+
position: "top-right",
|
|
252
|
+
offsetX: 8,
|
|
253
|
+
offsetY: 8,
|
|
254
|
+
blankY: 40,
|
|
255
|
+
zIndexBase: DEFAULT_Z_INDEX_BASE
|
|
256
|
+
},
|
|
257
|
+
API: { ...API }
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/client/core/api.ts
|
|
262
|
+
function toolDisableServerKey(server, state) {
|
|
263
|
+
if (server.scope === "global") return `@@global/${server.name}`;
|
|
264
|
+
const root = state.projectRoot;
|
|
265
|
+
if (typeof root !== "string" || root === "") return void 0;
|
|
266
|
+
return `@${root}/${server.name}`;
|
|
267
|
+
}
|
|
268
|
+
function cwdQueryOf(state) {
|
|
269
|
+
return typeof state.currentCwd === "string" && state.currentCwd !== "" ? `&cwd=${encodeURIComponent(state.currentCwd)}` : "";
|
|
270
|
+
}
|
|
271
|
+
function resumeBodyOf(state) {
|
|
272
|
+
if (!state.sessionResolved) return void 0;
|
|
273
|
+
return { cwd: typeof state.currentCwd === "string" ? state.currentCwd : "" };
|
|
274
|
+
}
|
|
275
|
+
async function api(path, options = {}) {
|
|
276
|
+
const timeoutMs = options.timeoutMs ?? 1e4;
|
|
277
|
+
const hasCallerSignal = options.signal !== void 0;
|
|
278
|
+
const controller = new AbortController();
|
|
279
|
+
const timer = hasCallerSignal ? void 0 : setTimeout(
|
|
280
|
+
() => controller.abort(new Error(`request timed out (${timeoutMs}ms)`)),
|
|
281
|
+
timeoutMs
|
|
282
|
+
);
|
|
283
|
+
const merged = { ...options, signal: options.signal ?? controller.signal };
|
|
284
|
+
try {
|
|
285
|
+
const response = await fetch(path, merged);
|
|
286
|
+
let body;
|
|
287
|
+
try {
|
|
288
|
+
body = await response.json();
|
|
289
|
+
} catch {
|
|
290
|
+
body = void 0;
|
|
291
|
+
}
|
|
292
|
+
if (!response.ok) {
|
|
293
|
+
const msg = body?.error ?? `HTTP ${response.status}`;
|
|
294
|
+
throw new Error(msg);
|
|
295
|
+
}
|
|
296
|
+
return body;
|
|
297
|
+
} finally {
|
|
298
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/client/core/dom.ts
|
|
303
|
+
function pangu(text) {
|
|
304
|
+
return String(text).replace(/([㐀-䶿一-鿿豈-])([A-Za-z0-9@#$%^&*()[\]{}<>+\-=/\\|])/g, "$1 $2").replace(/([A-Za-z0-9@#$%^&*()[\]{}<>+\-=/\\|])([㐀-䶿一-鿿豈-])/g, "$1 $2");
|
|
305
|
+
}
|
|
306
|
+
function applyAttrs(node, attrs) {
|
|
307
|
+
for (const [key, value] of Object.entries(attrs)) {
|
|
308
|
+
if (key === "class") node.className = value == null ? "" : String(value);
|
|
309
|
+
else if (key === "text") node.textContent = String(value);
|
|
310
|
+
else if (key === "dataset") Object.assign(node.dataset, value);
|
|
311
|
+
else if (key.startsWith("on")) node.addEventListener(key.slice(2), value);
|
|
312
|
+
else if (key === "checked") node.checked = value;
|
|
313
|
+
else if (key === "disabled") node.disabled = value;
|
|
314
|
+
else if (key === "children") continue;
|
|
315
|
+
else node.setAttribute(key, String(value));
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
function appendKids(node, kids) {
|
|
319
|
+
for (const child of kids) {
|
|
320
|
+
if (child === void 0 || child === null) continue;
|
|
321
|
+
node.appendChild(typeof child === "string" ? document.createTextNode(child) : child);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function el(tag, attrs = {}, children) {
|
|
325
|
+
const kids = children ?? attrs.children ?? [];
|
|
326
|
+
const node = document.createElement(tag);
|
|
327
|
+
applyAttrs(node, attrs);
|
|
328
|
+
appendKids(node, kids);
|
|
329
|
+
return node;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// src/vendor/shared/client/i18n.ts
|
|
333
|
+
var t = function(key) {
|
|
334
|
+
return key;
|
|
335
|
+
};
|
|
336
|
+
var fallbackDict = null;
|
|
337
|
+
function setFallbackDict(dict) {
|
|
338
|
+
fallbackDict = dict;
|
|
339
|
+
}
|
|
340
|
+
function fromFallback(key, params) {
|
|
341
|
+
const raw = fallbackDict?.[key];
|
|
342
|
+
if (raw === void 0) return key;
|
|
343
|
+
if (params === void 0) return raw;
|
|
344
|
+
return raw.replace(
|
|
345
|
+
/\{(\w+)\}/g,
|
|
346
|
+
(m, name) => Object.prototype.hasOwnProperty.call(params, name) ? String(params[name]) : m
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
function bindLocale(locale, ns) {
|
|
350
|
+
if (locale && typeof locale.bind === "function") {
|
|
351
|
+
t = locale.bind(ns);
|
|
352
|
+
} else if (fallbackDict !== null) {
|
|
353
|
+
t = (key, params) => fromFallback(key, params);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// src/client/core/i18n.ts
|
|
358
|
+
function tStatus(status) {
|
|
359
|
+
return STATUS_TEXT[status] !== void 0 ? t(STATUS_TEXT[status]) : status;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// src/client/float/servers.ts
|
|
363
|
+
function endpointOf(server) {
|
|
364
|
+
if (server.transport === "streamable-http") return server.url ?? "";
|
|
365
|
+
const args = Array.isArray(server.args) && server.args.length > 0 ? ` ${server.args.join(" ")}` : "";
|
|
366
|
+
return `${server.command ?? ""}${args}`;
|
|
367
|
+
}
|
|
368
|
+
function actionButton(label, onClick, primary = false, danger = false) {
|
|
369
|
+
const button = el("button", {
|
|
370
|
+
class: `${primary ? "dm-primary" : ""} ${danger ? "dm-danger" : ""}`.trim(),
|
|
371
|
+
text: label
|
|
372
|
+
});
|
|
373
|
+
button.addEventListener("click", async () => {
|
|
374
|
+
const original = label;
|
|
375
|
+
button.disabled = true;
|
|
376
|
+
button.style.opacity = "0.6";
|
|
377
|
+
button.textContent = `${original}…`;
|
|
378
|
+
try {
|
|
379
|
+
await onClick();
|
|
380
|
+
} catch (error) {
|
|
381
|
+
window.alert(
|
|
382
|
+
t("actionFail", { msg: error instanceof Error ? error.message : String(error) })
|
|
383
|
+
);
|
|
384
|
+
} finally {
|
|
385
|
+
button.disabled = false;
|
|
386
|
+
button.style.opacity = "";
|
|
387
|
+
button.textContent = original;
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
return button;
|
|
391
|
+
}
|
|
392
|
+
function toolCheckbox(server, tool, disabled, state, actions) {
|
|
393
|
+
const label = el("label", { class: "dm-tool" });
|
|
394
|
+
const input = el("input", { type: "checkbox", checked: disabled });
|
|
395
|
+
input.addEventListener("change", () => {
|
|
396
|
+
const serverKey = toolDisableServerKey(server, state);
|
|
397
|
+
if (serverKey === void 0) {
|
|
398
|
+
input.checked = !input.checked;
|
|
399
|
+
console.warn("[dsh-mcp-manager] projectRoot 缺失,跳过 tool-disable(非法 @/name 防御)");
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
void api(state.API.toolDisable, {
|
|
403
|
+
method: "PATCH",
|
|
404
|
+
headers: { "content-type": "application/json" },
|
|
405
|
+
body: JSON.stringify({
|
|
406
|
+
server: serverKey,
|
|
407
|
+
tool,
|
|
408
|
+
disabled: input.checked
|
|
409
|
+
})
|
|
410
|
+
}).then(() => actions.refresh()).catch((error) => {
|
|
411
|
+
input.checked = !input.checked;
|
|
412
|
+
console.warn("[dsh-mcp-manager] tool-disable failed:", error);
|
|
413
|
+
});
|
|
414
|
+
});
|
|
415
|
+
label.appendChild(input);
|
|
416
|
+
label.appendChild(document.createTextNode(tool));
|
|
417
|
+
return label;
|
|
418
|
+
}
|
|
419
|
+
function serverToolsDetails(server, toolCount, opts, state, actions, interactive) {
|
|
420
|
+
const details = el("details", { class: "dm-tools", dataset: { dmServer: server.name } });
|
|
421
|
+
if (opts.openTools?.has(server.name) === true) details.open = true;
|
|
422
|
+
details.appendChild(el("summary", { text: t("toolsCount", { n: toolCount }) }));
|
|
423
|
+
const list = el("ul");
|
|
424
|
+
const tools = Array.isArray(server.tools) ? server.tools : [];
|
|
425
|
+
if (interactive) {
|
|
426
|
+
const disabledSet = new Set(Array.isArray(server.disabledTools) ? server.disabledTools : []);
|
|
427
|
+
for (const tool of tools) {
|
|
428
|
+
list.appendChild(
|
|
429
|
+
el("li", {}, [toolCheckbox(server, tool, disabledSet.has(tool), state, actions)])
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
} else {
|
|
433
|
+
for (const tool of tools) list.appendChild(el("li", { text: tool }));
|
|
434
|
+
}
|
|
435
|
+
details.appendChild(list);
|
|
436
|
+
return details;
|
|
437
|
+
}
|
|
438
|
+
function jsonPatchInit(enabled) {
|
|
439
|
+
return {
|
|
440
|
+
method: "PATCH",
|
|
441
|
+
headers: { "content-type": "application/json" },
|
|
442
|
+
body: JSON.stringify({ enabled })
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
function postThenRefresh(state, actions, url) {
|
|
446
|
+
return async () => {
|
|
447
|
+
await api(url, { method: "POST" });
|
|
448
|
+
await actions.refresh();
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
function activateSourceRun(server, state, actions) {
|
|
452
|
+
return async () => {
|
|
453
|
+
const q = cwdQueryOf(state).replace(/^&/, "");
|
|
454
|
+
await api(`${state.API.sourceActivate}${q ? "?" + q : ""}`, {
|
|
455
|
+
method: "POST",
|
|
456
|
+
headers: { "content-type": "application/json" },
|
|
457
|
+
body: JSON.stringify({ name: server.name, id: server.source })
|
|
458
|
+
});
|
|
459
|
+
await actions.refresh();
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
function statusActions(server, state, actions, scopeQuery, cwdQuery) {
|
|
463
|
+
const name = encodeURIComponent(server.name);
|
|
464
|
+
if (server.shadowedBy !== void 0) {
|
|
465
|
+
return [
|
|
466
|
+
{
|
|
467
|
+
label: t("connect"),
|
|
468
|
+
primary: true,
|
|
469
|
+
run: activateSourceRun(server, state, actions)
|
|
470
|
+
}
|
|
471
|
+
];
|
|
472
|
+
}
|
|
473
|
+
if (server.status === "connected") {
|
|
474
|
+
return [
|
|
475
|
+
{
|
|
476
|
+
label: t("disconnect"),
|
|
477
|
+
primary: false,
|
|
478
|
+
run: postThenRefresh(
|
|
479
|
+
state,
|
|
480
|
+
actions,
|
|
481
|
+
`${state.API.disconnect}?name=${name}${scopeQuery}${cwdQuery}`
|
|
482
|
+
)
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
label: t("reconnect"),
|
|
486
|
+
primary: false,
|
|
487
|
+
run: postThenRefresh(
|
|
488
|
+
state,
|
|
489
|
+
actions,
|
|
490
|
+
`${state.API.reconnect}?name=${name}${scopeQuery}${cwdQuery}`
|
|
491
|
+
)
|
|
492
|
+
}
|
|
493
|
+
];
|
|
494
|
+
}
|
|
495
|
+
if (server.status === "disabled") {
|
|
496
|
+
return [
|
|
497
|
+
{
|
|
498
|
+
label: t("enableAndConnect"),
|
|
499
|
+
primary: true,
|
|
500
|
+
run: async () => {
|
|
501
|
+
await api(`${state.API.servers}?name=${name}${scopeQuery}`, jsonPatchInit(true));
|
|
502
|
+
await api(`${state.API.connect}?name=${name}${scopeQuery}${cwdQuery}`, {
|
|
503
|
+
method: "POST"
|
|
504
|
+
});
|
|
505
|
+
await actions.refresh();
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
];
|
|
509
|
+
}
|
|
510
|
+
return [
|
|
511
|
+
{
|
|
512
|
+
label: t("connect"),
|
|
513
|
+
primary: true,
|
|
514
|
+
run: postThenRefresh(
|
|
515
|
+
state,
|
|
516
|
+
actions,
|
|
517
|
+
`${state.API.connect}?name=${name}${scopeQuery}${cwdQuery}`
|
|
518
|
+
)
|
|
519
|
+
}
|
|
520
|
+
];
|
|
521
|
+
}
|
|
522
|
+
function disableAction(server, state, actions, scopeQuery, cwdQuery) {
|
|
523
|
+
const name = encodeURIComponent(server.name);
|
|
524
|
+
return {
|
|
525
|
+
label: t("disable"),
|
|
526
|
+
primary: false,
|
|
527
|
+
run: async () => {
|
|
528
|
+
await api(
|
|
529
|
+
`${state.API.servers}?name=${name}${scopeQuery}${cwdQuery}`,
|
|
530
|
+
jsonPatchInit(false)
|
|
531
|
+
);
|
|
532
|
+
if (state.editingName === server.name) actions.resetForm();
|
|
533
|
+
await actions.refresh();
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
function deleteAction(server, state, actions, scopeQuery) {
|
|
538
|
+
const name = encodeURIComponent(server.name);
|
|
539
|
+
return {
|
|
540
|
+
label: t("delete"),
|
|
541
|
+
primary: false,
|
|
542
|
+
danger: true,
|
|
543
|
+
run: async () => {
|
|
544
|
+
if (!window.confirm(t("confirmDelete", { name: server.name }))) return;
|
|
545
|
+
await api(`${state.API.servers}?name=${name}${scopeQuery}`, { method: "DELETE" });
|
|
546
|
+
if (state.editingName === server.name) actions.resetForm();
|
|
547
|
+
await actions.refresh();
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
function needsAttention(server) {
|
|
552
|
+
return server.status === "failed" || server.status === "reconnecting";
|
|
553
|
+
}
|
|
554
|
+
function serverCardClass(server) {
|
|
555
|
+
return needsAttention(server) ? "dm-server dm-server--fail" : "dm-server";
|
|
556
|
+
}
|
|
557
|
+
function serverCardHeader(server, toolCount) {
|
|
558
|
+
const isHttp = server.transport === "streamable-http";
|
|
559
|
+
const header = el("header");
|
|
560
|
+
header.appendChild(el("span", { class: "dm-name", text: server.name }));
|
|
561
|
+
header.appendChild(
|
|
562
|
+
el("span", {
|
|
563
|
+
class: `dm-badge ${isHttp ? "dm-http" : "dm-stdio"}`,
|
|
564
|
+
text: isHttp ? "HTTP" : "stdio"
|
|
565
|
+
})
|
|
566
|
+
);
|
|
567
|
+
header.appendChild(
|
|
568
|
+
el("span", {
|
|
569
|
+
class: "dm-badge",
|
|
570
|
+
text: server.scope === "project" ? t("badgeScopeProject") : t("badgeScopeGlobal")
|
|
571
|
+
})
|
|
572
|
+
);
|
|
573
|
+
header.appendChild(
|
|
574
|
+
el("span", { class: `dm-badge dm-st-${server.status}`, text: tStatus(server.status) })
|
|
575
|
+
);
|
|
576
|
+
header.appendChild(
|
|
577
|
+
el("span", { class: "dm-count", text: t("toolsCountPlain", { n: toolCount }) })
|
|
578
|
+
);
|
|
579
|
+
return header;
|
|
580
|
+
}
|
|
581
|
+
function renderServer(server, state, actions, opts = { tools: true }) {
|
|
582
|
+
const article = el("article", { class: serverCardClass(server) });
|
|
583
|
+
const toolCount = Array.isArray(server.tools) ? server.tools.length : 0;
|
|
584
|
+
article.appendChild(serverCardHeader(server, toolCount));
|
|
585
|
+
article.appendChild(el("div", { class: "dm-endpoint", text: endpointOf(server) }));
|
|
586
|
+
if (server.error !== void 0 && server.error !== "") {
|
|
587
|
+
article.appendChild(el("div", { class: "dm-err", text: server.error }));
|
|
588
|
+
}
|
|
589
|
+
if (toolCount > 0) {
|
|
590
|
+
article.appendChild(serverToolsDetails(server, toolCount, opts, state, actions, opts.tools));
|
|
591
|
+
}
|
|
592
|
+
const actionsEl = el("div", { class: "dm-actions" });
|
|
593
|
+
const busy = server.status === "connecting" || server.status === "reconnecting";
|
|
594
|
+
const scopeQuery = `&scope=${server.scope}`;
|
|
595
|
+
const cwdQuery = cwdQueryOf(state);
|
|
596
|
+
for (const spec of statusActions(server, state, actions, scopeQuery, cwdQuery)) {
|
|
597
|
+
actionsEl.appendChild(actionButton(spec.label, spec.run, spec.primary));
|
|
598
|
+
}
|
|
599
|
+
if (server.status !== "disabled") {
|
|
600
|
+
const disable = disableAction(server, state, actions, scopeQuery, cwdQuery);
|
|
601
|
+
actionsEl.appendChild(actionButton(disable.label, disable.run, disable.primary));
|
|
602
|
+
}
|
|
603
|
+
if (server.shadowedBy !== void 0) {
|
|
604
|
+
const shadowedBy = server.shadowedBy ?? "";
|
|
605
|
+
actionsEl.appendChild(
|
|
606
|
+
actionButton(t("srcActivateThis"), async () => {
|
|
607
|
+
try {
|
|
608
|
+
await activateSourceRun(server, state, actions)();
|
|
609
|
+
} catch (error) {
|
|
610
|
+
window.alert(t("importFail", { msg: error instanceof Error ? error.message : String(error) }));
|
|
611
|
+
}
|
|
612
|
+
})
|
|
613
|
+
);
|
|
614
|
+
actionsEl.appendChild(actionButton(t("cancel"), () => {
|
|
615
|
+
void actions.refresh();
|
|
616
|
+
}));
|
|
617
|
+
article.appendChild(actionsEl);
|
|
618
|
+
return article;
|
|
619
|
+
}
|
|
620
|
+
actionsEl.appendChild(actionButton(t("edit"), () => actions.beginEdit(server)));
|
|
621
|
+
const remove = deleteAction(server, state, actions, scopeQuery);
|
|
622
|
+
actionsEl.appendChild(
|
|
623
|
+
actionButton(remove.label, remove.run, remove.primary, remove.danger === true)
|
|
624
|
+
);
|
|
625
|
+
actionsEl.children[actionsEl.children.length - 1].disabled = busy;
|
|
626
|
+
article.appendChild(actionsEl);
|
|
627
|
+
return article;
|
|
628
|
+
}
|
|
629
|
+
var groupFoldOpen = /* @__PURE__ */ new Map();
|
|
630
|
+
function renderServers(state, actions) {
|
|
631
|
+
if (state.bodyEl === void 0) return;
|
|
632
|
+
const openTools = /* @__PURE__ */ new Set();
|
|
633
|
+
for (const d of state.bodyEl.querySelectorAll("details.dm-tools")) {
|
|
634
|
+
if (d.open && d.dataset.dmServer !== void 0) openTools.add(d.dataset.dmServer);
|
|
635
|
+
}
|
|
636
|
+
state.bodyEl.textContent = "";
|
|
637
|
+
if (state.servers.length === 0) {
|
|
638
|
+
state.bodyEl.appendChild(
|
|
639
|
+
el("div", { class: "dm-status", children: [document.createTextNode(t("serversEmpty"))] })
|
|
640
|
+
);
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
const SOURCE_GROUPS = [
|
|
644
|
+
{ id: "global-dsh", labelKey: "srcGlobalDsh" },
|
|
645
|
+
{ id: "global-agents", labelKey: "srcGlobalAgents" },
|
|
646
|
+
{ id: "project-dsh", labelKey: "srcProjectDsh" },
|
|
647
|
+
{ id: "project-agents", labelKey: "srcProjectAgents" }
|
|
648
|
+
];
|
|
649
|
+
const rest = state.servers;
|
|
650
|
+
const appendGroup = (sourceId, label, list) => {
|
|
651
|
+
const connCount = list.filter((s) => s.status === "connected").length;
|
|
652
|
+
const failCount = list.filter((s) => s.status === "failed" || s.status === "reconnecting").length;
|
|
653
|
+
const title = `${label} (${list.length}) · 运行 ${connCount} · 失败 ${failCount}`;
|
|
654
|
+
const section = el("section", {
|
|
655
|
+
class: "dm-group",
|
|
656
|
+
style: "border:1px solid var(--dsw-alias-border-l2, #e2e4ea);border-radius:14px;padding:10px 14px;margin:0 0 14px;"
|
|
657
|
+
});
|
|
658
|
+
const titleEl = el("h3", {
|
|
659
|
+
style: "cursor:pointer;user-select:none;margin:0 0 10px;"
|
|
660
|
+
});
|
|
661
|
+
const wrap = el("div", {
|
|
662
|
+
style: `display:${groupFoldOpen.get(sourceId) ? "" : "none"};`
|
|
663
|
+
// 默认折叠;折叠态跨刷新记忆
|
|
664
|
+
});
|
|
665
|
+
const setArrow = () => {
|
|
666
|
+
titleEl.replaceChildren(
|
|
667
|
+
document.createTextNode(`${wrap.style.display === "none" ? "▸" : "▾"} ${title} `)
|
|
668
|
+
);
|
|
669
|
+
};
|
|
670
|
+
titleEl.addEventListener("click", () => {
|
|
671
|
+
wrap.style.display = wrap.style.display === "none" ? "" : "none";
|
|
672
|
+
groupFoldOpen.set(sourceId, wrap.style.display !== "none");
|
|
673
|
+
setArrow();
|
|
674
|
+
});
|
|
675
|
+
setArrow();
|
|
676
|
+
const headRow = el("div", { style: "display:flex;align-items:center;gap:12px;" });
|
|
677
|
+
headRow.appendChild(titleEl);
|
|
678
|
+
const quickLink = el("span", {
|
|
679
|
+
text: "+ " + t("srcQuickSetup"),
|
|
680
|
+
style: "cursor:pointer;font-size:12px;color:var(--dsw-alias-state-business-primary, #2f6fe0);user-select:none;"
|
|
681
|
+
});
|
|
682
|
+
quickLink.addEventListener("click", () => {
|
|
683
|
+
actions.switchTab("quick");
|
|
684
|
+
requestAnimationFrame(() => {
|
|
685
|
+
state.bodyEl?.ownerDocument?.querySelector(`[data-dm-src="${sourceId}"]`)?.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
686
|
+
});
|
|
687
|
+
});
|
|
688
|
+
headRow.appendChild(quickLink);
|
|
689
|
+
section.appendChild(headRow);
|
|
690
|
+
section.appendChild(wrap);
|
|
691
|
+
for (const server of [...list].sort(
|
|
692
|
+
(a, b) => a.name.localeCompare(b.name)
|
|
693
|
+
)) {
|
|
694
|
+
const card = renderServer(server, state, actions, { tools: true, openTools });
|
|
695
|
+
card.style.marginLeft = "18px";
|
|
696
|
+
wrap.appendChild(card);
|
|
697
|
+
}
|
|
698
|
+
state.bodyEl.appendChild(section);
|
|
699
|
+
};
|
|
700
|
+
for (const group of SOURCE_GROUPS) {
|
|
701
|
+
const list = rest.filter((server) => server.source === group.id);
|
|
702
|
+
appendGroup(group.id, `${t(group.labelKey)}`, list);
|
|
703
|
+
}
|
|
704
|
+
const ungrouped = rest.filter(
|
|
705
|
+
(server) => !SOURCE_GROUPS.some((g) => g.id === server.source)
|
|
706
|
+
);
|
|
707
|
+
if (ungrouped.length > 0) {
|
|
708
|
+
appendGroup("other", `${t("srcUngrouped")}`, ungrouped);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
// src/client/float/quick-add.ts
|
|
713
|
+
function formScopeValue(state) {
|
|
714
|
+
return state.projectRoot !== void 0 && state.projectRoot !== "" ? "project" : "global";
|
|
715
|
+
}
|
|
716
|
+
function currentCwdBody(state) {
|
|
717
|
+
return typeof state.currentCwd === "string" && state.currentCwd !== "" ? { cwd: state.currentCwd } : {};
|
|
718
|
+
}
|
|
719
|
+
function isEditing(state) {
|
|
720
|
+
return state.editingName !== void 0;
|
|
721
|
+
}
|
|
722
|
+
function resetForm(state) {
|
|
723
|
+
state.editingName = void 0;
|
|
724
|
+
state.editing = void 0;
|
|
725
|
+
}
|
|
726
|
+
function isMigratedEdit(state, server, payload) {
|
|
727
|
+
const editingName = state.editingName;
|
|
728
|
+
if (editingName === void 0) return false;
|
|
729
|
+
const serverName = typeof server.name === "string" ? server.name : "";
|
|
730
|
+
if (editingName !== serverName) return true;
|
|
731
|
+
const editScope = state.editing?.scope;
|
|
732
|
+
return editScope !== void 0 && editScope !== payload.scope;
|
|
733
|
+
}
|
|
734
|
+
function jsonWriteInit(payload) {
|
|
735
|
+
return {
|
|
736
|
+
method: "POST",
|
|
737
|
+
headers: { "content-type": "application/json" },
|
|
738
|
+
body: JSON.stringify(payload)
|
|
739
|
+
};
|
|
740
|
+
}
|
|
741
|
+
async function performSaveRequest(state, server, payload) {
|
|
742
|
+
if (isMigratedEdit(state, server, payload)) {
|
|
743
|
+
if (state.editing?.hasSecrets === true) {
|
|
744
|
+
window.alert(
|
|
745
|
+
t("saveFail", {
|
|
746
|
+
msg: "改名/改归属迁移不会带入原有凭据,请新建同名条目并填写真实凭据,确认可用后再删除旧条目"
|
|
747
|
+
})
|
|
748
|
+
);
|
|
749
|
+
return false;
|
|
750
|
+
}
|
|
751
|
+
await api(state.API.servers, jsonWriteInit(payload));
|
|
752
|
+
await api(
|
|
753
|
+
`${state.API.servers}?name=${encodeURIComponent(state.editingName ?? "")}&scope=${state.editing?.scope}`,
|
|
754
|
+
{ method: "DELETE" }
|
|
755
|
+
);
|
|
756
|
+
return true;
|
|
757
|
+
}
|
|
758
|
+
if (isEditing(state)) {
|
|
759
|
+
const query = `name=${encodeURIComponent(state.editingName ?? "")}&scope=${formScopeValue(state)}`;
|
|
760
|
+
await api(`${state.API.servers}?${query}`, {
|
|
761
|
+
...jsonWriteInit(payload),
|
|
762
|
+
method: "PATCH"
|
|
763
|
+
});
|
|
764
|
+
return true;
|
|
765
|
+
}
|
|
766
|
+
await api(state.API.servers, jsonWriteInit(payload));
|
|
767
|
+
return true;
|
|
768
|
+
}
|
|
769
|
+
function beginEdit(state, actions, server) {
|
|
770
|
+
state.editingName = server.name;
|
|
771
|
+
state.editing = server;
|
|
772
|
+
openJsonEditor(state, actions, server);
|
|
773
|
+
}
|
|
774
|
+
function editorJson(server) {
|
|
775
|
+
const out = { name: server.name, transport: server.transport };
|
|
776
|
+
const copy = (key) => {
|
|
777
|
+
if (server[key] !== void 0) out[key] = server[key];
|
|
778
|
+
};
|
|
779
|
+
for (const key of [
|
|
780
|
+
"command",
|
|
781
|
+
"args",
|
|
782
|
+
"env",
|
|
783
|
+
"cwd",
|
|
784
|
+
"url",
|
|
785
|
+
"headers",
|
|
786
|
+
"enabled",
|
|
787
|
+
"toolCallTimeoutMs",
|
|
788
|
+
"description"
|
|
789
|
+
]) {
|
|
790
|
+
copy(key);
|
|
791
|
+
}
|
|
792
|
+
return JSON.stringify(out, null, 2);
|
|
793
|
+
}
|
|
794
|
+
function openJsonEditor(state, actions, server) {
|
|
795
|
+
const overlay = el("div", {
|
|
796
|
+
class: "dm-overlay dm-overlay--open",
|
|
797
|
+
style: "z-index:10001"
|
|
798
|
+
});
|
|
799
|
+
const card = el("div", {
|
|
800
|
+
class: "dm-card",
|
|
801
|
+
style: "width:min(880px,94vw);max-height:88vh;display:flex;flex-direction:column;overflow:hidden"
|
|
802
|
+
});
|
|
803
|
+
const close2 = () => {
|
|
804
|
+
document.removeEventListener("keydown", onKey, true);
|
|
805
|
+
overlay.remove();
|
|
806
|
+
};
|
|
807
|
+
const onKey = (event) => {
|
|
808
|
+
if (event.key !== "Escape") return;
|
|
809
|
+
event.preventDefault();
|
|
810
|
+
event.stopPropagation();
|
|
811
|
+
close2();
|
|
812
|
+
};
|
|
813
|
+
document.addEventListener("keydown", onKey, true);
|
|
814
|
+
const head = el("div", {
|
|
815
|
+
class: "dm-head",
|
|
816
|
+
style: "display:flex;align-items:center;justify-content:space-between;"
|
|
817
|
+
});
|
|
818
|
+
head.appendChild(el("h2", { style: "margin:0;", text: t("editServer", { name: server.name }) }));
|
|
819
|
+
const closeBtn = el("button", { text: t("close"), onclick: close2 });
|
|
820
|
+
head.appendChild(closeBtn);
|
|
821
|
+
card.appendChild(head);
|
|
822
|
+
const body = el("div", { class: "dm-body", style: "display:flex;flex-direction:column;gap:10px" });
|
|
823
|
+
const area = el("textarea", {
|
|
824
|
+
class: "dm-full",
|
|
825
|
+
style: "flex:1;min-height:420px;width:100%;border:1px solid #d7dae0;border-radius:8px;padding:10px 12px;font-size:13px;line-height:1.55;font-family:ui-monospace,Consolas,monospace;box-sizing:border-box;resize:vertical"
|
|
826
|
+
});
|
|
827
|
+
area.value = editorJson(server);
|
|
828
|
+
body.appendChild(area);
|
|
829
|
+
const result = el("div", { class: "dm-result" });
|
|
830
|
+
body.appendChild(result);
|
|
831
|
+
const actionsRow = el("div", { class: "dm-actions" });
|
|
832
|
+
actionsRow.appendChild(
|
|
833
|
+
el("button", {
|
|
834
|
+
class: "dm-primary",
|
|
835
|
+
text: t("srcSave"),
|
|
836
|
+
onclick: async () => {
|
|
837
|
+
try {
|
|
838
|
+
const payload = JSON.parse(area.value);
|
|
839
|
+
if (typeof payload.name === "string" && payload.name !== server.name) {
|
|
840
|
+
throw new Error(t("jsonNameFixed"));
|
|
841
|
+
}
|
|
842
|
+
payload.name = server.name;
|
|
843
|
+
payload.transport = server.transport;
|
|
844
|
+
const ok = await performSaveRequest(
|
|
845
|
+
state,
|
|
846
|
+
server,
|
|
847
|
+
payload
|
|
848
|
+
);
|
|
849
|
+
if (!ok) return;
|
|
850
|
+
result.textContent = t("srcSavedOk", { names: server.name });
|
|
851
|
+
close2();
|
|
852
|
+
await actions.refresh();
|
|
853
|
+
} catch (error) {
|
|
854
|
+
result.textContent = t("importFail", {
|
|
855
|
+
msg: error instanceof Error ? error.message : String(error)
|
|
856
|
+
});
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
})
|
|
860
|
+
);
|
|
861
|
+
actionsRow.appendChild(el("button", { text: t("cancel"), onclick: close2 }));
|
|
862
|
+
body.appendChild(actionsRow);
|
|
863
|
+
card.appendChild(body);
|
|
864
|
+
overlay.appendChild(card);
|
|
865
|
+
overlay.addEventListener("click", (event) => {
|
|
866
|
+
if (event.target === overlay) close2();
|
|
867
|
+
});
|
|
868
|
+
document.body.appendChild(overlay);
|
|
869
|
+
}
|
|
870
|
+
function buildQuickAdd(state, actions) {
|
|
871
|
+
const page = el("div");
|
|
872
|
+
const srcSection = el("section", { class: "dm-section" });
|
|
873
|
+
srcSection.appendChild(el("h3", { text: t("sourcesTitle") }));
|
|
874
|
+
const srcHint = el("div", { class: "dm-result", text: "" });
|
|
875
|
+
const srcBox = el("div", { class: "dm-paste-box" });
|
|
876
|
+
const currentCwd = () => currentCwdBody(state);
|
|
877
|
+
const loadSources = async () => {
|
|
878
|
+
try {
|
|
879
|
+
const payload = await api(
|
|
880
|
+
`${state.API.sources}?cwd=${encodeURIComponent(String(currentCwd().cwd ?? ""))}`,
|
|
881
|
+
{ method: "GET" }
|
|
882
|
+
);
|
|
883
|
+
srcBox.innerHTML = "";
|
|
884
|
+
for (const source of payload.sources) {
|
|
885
|
+
const card = el("div", { class: "dm-server", style: "margin-bottom:14px;" });
|
|
886
|
+
card.dataset.dmSrc = source.id;
|
|
887
|
+
card.appendChild(el("div", {
|
|
888
|
+
class: "dm-name",
|
|
889
|
+
text: t(
|
|
890
|
+
source.id === "global-agents" ? "srcGlobalAgents" : source.id === "global-dsh" ? "srcGlobalDsh" : source.id === "project-agents" ? "srcProjectAgents" : "srcProjectDsh"
|
|
891
|
+
)
|
|
892
|
+
}));
|
|
893
|
+
card.appendChild(el("div", {
|
|
894
|
+
class: "dm-skip",
|
|
895
|
+
text: (source.path ?? t("srcNeedSession")) + (source.exists ? "" : "(" + t("srcNotExists") + ")")
|
|
896
|
+
}));
|
|
897
|
+
if (source.error !== null) {
|
|
898
|
+
card.appendChild(el("div", { class: "dm-skip", text: t("importFail", { msg: source.error }) }));
|
|
899
|
+
}
|
|
900
|
+
const result = el("div", { class: "dm-result" });
|
|
901
|
+
card.appendChild(result);
|
|
902
|
+
card.appendChild(
|
|
903
|
+
el("div", {
|
|
904
|
+
class: "dm-actions",
|
|
905
|
+
children: [
|
|
906
|
+
el("button", {
|
|
907
|
+
class: "dm-primary",
|
|
908
|
+
text: t("srcEdit"),
|
|
909
|
+
onclick: () => openSourceEditor(state, actions, source, currentCwd, result, loadSources)
|
|
910
|
+
})
|
|
911
|
+
]
|
|
912
|
+
})
|
|
913
|
+
);
|
|
914
|
+
srcBox.appendChild(card);
|
|
915
|
+
}
|
|
916
|
+
srcHint.textContent = "";
|
|
917
|
+
} catch (error) {
|
|
918
|
+
srcHint.textContent = t("importFail", { msg: error instanceof Error ? error.message : String(error) });
|
|
919
|
+
}
|
|
920
|
+
};
|
|
921
|
+
srcSection.appendChild(srcHint);
|
|
922
|
+
srcSection.appendChild(srcBox);
|
|
923
|
+
void loadSources();
|
|
924
|
+
page.appendChild(srcSection);
|
|
925
|
+
return page;
|
|
926
|
+
}
|
|
927
|
+
function openSourceEditor(state, actions, source, currentCwd, result, onSaved) {
|
|
928
|
+
const overlay = el("div", {
|
|
929
|
+
class: "dm-overlay dm-overlay--open",
|
|
930
|
+
style: "z-index:10001"
|
|
931
|
+
});
|
|
932
|
+
const card = el("div", {
|
|
933
|
+
class: "dm-card",
|
|
934
|
+
style: "width:min(880px,94vw);max-height:88vh;display:flex;flex-direction:column;overflow:hidden"
|
|
935
|
+
});
|
|
936
|
+
const close2 = () => {
|
|
937
|
+
document.removeEventListener("keydown", onKey, true);
|
|
938
|
+
overlay.remove();
|
|
939
|
+
};
|
|
940
|
+
const onKey = (event) => {
|
|
941
|
+
if (event.key !== "Escape") return;
|
|
942
|
+
event.preventDefault();
|
|
943
|
+
event.stopPropagation();
|
|
944
|
+
close2();
|
|
945
|
+
};
|
|
946
|
+
document.addEventListener("keydown", onKey, true);
|
|
947
|
+
const head = el("div", {
|
|
948
|
+
class: "dm-head",
|
|
949
|
+
style: "display:flex;align-items:center;justify-content:space-between;"
|
|
950
|
+
});
|
|
951
|
+
head.appendChild(el("h2", {
|
|
952
|
+
style: "margin:0;",
|
|
953
|
+
text: source.id === "global-agents" ? t("srcGlobalAgents") : source.id === "global-dsh" ? t("srcGlobalDsh") : source.id === "project-agents" ? t("srcProjectAgents") : t("srcProjectDsh")
|
|
954
|
+
}));
|
|
955
|
+
const closeBtn = el("button", { text: t("close"), onclick: close2 });
|
|
956
|
+
head.appendChild(closeBtn);
|
|
957
|
+
card.appendChild(head);
|
|
958
|
+
if (source.path !== null) {
|
|
959
|
+
const pathLine = el("div", {
|
|
960
|
+
class: "dm-skip",
|
|
961
|
+
style: "margin:8px 0 10px;padding-left:24px;font-family:ui-monospace,Consolas,monospace;font-size:12px;",
|
|
962
|
+
text: source.path
|
|
963
|
+
});
|
|
964
|
+
card.appendChild(pathLine);
|
|
965
|
+
}
|
|
966
|
+
const body = el("div", { class: "dm-body", style: "display:flex;flex-direction:column;gap:10px" });
|
|
967
|
+
const area = el("textarea", {
|
|
968
|
+
class: "dm-full",
|
|
969
|
+
style: "flex:1;min-height:420px;width:100%;border:1px solid #d7dae0;border-radius:8px;padding:10px 12px;font-size:13px;line-height:1.55;font-family:ui-monospace,Consolas,monospace;box-sizing:border-box;resize:vertical"
|
|
970
|
+
});
|
|
971
|
+
area.value = source.text ?? "";
|
|
972
|
+
body.appendChild(area);
|
|
973
|
+
const modalResult = el("div", { class: "dm-result" });
|
|
974
|
+
body.appendChild(modalResult);
|
|
975
|
+
const actionsRow = el("div", { class: "dm-actions" });
|
|
976
|
+
actionsRow.appendChild(
|
|
977
|
+
el("button", {
|
|
978
|
+
class: "dm-primary",
|
|
979
|
+
text: t("srcSave"),
|
|
980
|
+
onclick: async () => {
|
|
981
|
+
try {
|
|
982
|
+
const payload2 = await api(
|
|
983
|
+
state.API.sourceSave,
|
|
984
|
+
{
|
|
985
|
+
method: "POST",
|
|
986
|
+
headers: { "content-type": "application/json" },
|
|
987
|
+
body: JSON.stringify({ id: source.id, text: area.value, ...currentCwd() })
|
|
988
|
+
}
|
|
989
|
+
);
|
|
990
|
+
const names = payload2.effective.map((e) => e.name).join(", ") || t("importedNone");
|
|
991
|
+
modalResult.textContent = t("srcSavedOk", { names });
|
|
992
|
+
if (payload2.removed.length > 0) {
|
|
993
|
+
modalResult.appendChild(el("div", { class: "dm-skip", text: t("srcMutexRemoved", { names: payload2.removed.map((r) => r.name).join(", ") }) }));
|
|
994
|
+
}
|
|
995
|
+
for (const warning of payload2.warnings) {
|
|
996
|
+
modalResult.appendChild(el("div", { class: "dm-skip", text: warning }));
|
|
997
|
+
}
|
|
998
|
+
result.textContent = t("srcSavedOk", { names });
|
|
999
|
+
await actions.refresh();
|
|
1000
|
+
} catch (error) {
|
|
1001
|
+
modalResult.textContent = t("importFail", { msg: error instanceof Error ? error.message : String(error) });
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
})
|
|
1005
|
+
);
|
|
1006
|
+
body.appendChild(actionsRow);
|
|
1007
|
+
card.appendChild(body);
|
|
1008
|
+
overlay.appendChild(card);
|
|
1009
|
+
document.body.appendChild(overlay);
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
// src/client/core/current-session.ts
|
|
1013
|
+
var UNKNOWN = { kind: "unknown" };
|
|
1014
|
+
function isObject(value) {
|
|
1015
|
+
return typeof value === "object" && value !== null;
|
|
1016
|
+
}
|
|
1017
|
+
function isMainViewRow(row) {
|
|
1018
|
+
if (!isObject(row)) return false;
|
|
1019
|
+
const retained = row.retainedBy;
|
|
1020
|
+
if (retained === void 0 || retained === null) return false;
|
|
1021
|
+
const count = retained.mainView;
|
|
1022
|
+
return typeof count === "number" && count > 0;
|
|
1023
|
+
}
|
|
1024
|
+
function readSnapshot(list) {
|
|
1025
|
+
try {
|
|
1026
|
+
const snapshot = list.getSnapshot();
|
|
1027
|
+
return isObject(snapshot) ? snapshot : void 0;
|
|
1028
|
+
} catch {
|
|
1029
|
+
return void 0;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
function firstMainViewRow(state) {
|
|
1033
|
+
const byId = state.byId;
|
|
1034
|
+
if (!isObject(byId)) return void 0;
|
|
1035
|
+
const rows = byId;
|
|
1036
|
+
for (const value of Object.values(rows)) {
|
|
1037
|
+
if (!isObject(value)) continue;
|
|
1038
|
+
const row = value;
|
|
1039
|
+
if (isMainViewRow(row)) return row;
|
|
1040
|
+
}
|
|
1041
|
+
return void 0;
|
|
1042
|
+
}
|
|
1043
|
+
function readCurrentSession(list) {
|
|
1044
|
+
if (list === void 0 || typeof list.getSnapshot !== "function") return UNKNOWN;
|
|
1045
|
+
const state = readSnapshot(list);
|
|
1046
|
+
if (state === void 0) return UNKNOWN;
|
|
1047
|
+
const row = firstMainViewRow(state);
|
|
1048
|
+
if (row === void 0) return UNKNOWN;
|
|
1049
|
+
return { kind: "session", row, cwd: row.cwd, blank: row.blank === true };
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
// src/client/float/float.ts
|
|
1053
|
+
function replayAnim(node) {
|
|
1054
|
+
const style = node.style;
|
|
1055
|
+
style.animation = "none";
|
|
1056
|
+
void node.offsetWidth;
|
|
1057
|
+
style.animation = "";
|
|
1058
|
+
}
|
|
1059
|
+
function pillDotColor(fail, connected) {
|
|
1060
|
+
if (fail) return "var(--dsw-alias-state-error-primary,#e0483e)";
|
|
1061
|
+
if (connected > 0) return "var(--dsw-alias-state-success-primary,#0f9d6e)";
|
|
1062
|
+
return "var(--dsw-alias-label-tertiary,#9aa1ad)";
|
|
1063
|
+
}
|
|
1064
|
+
function pillHints(total, bad, connected) {
|
|
1065
|
+
const failed = t("healthFailed", { n: bad });
|
|
1066
|
+
return {
|
|
1067
|
+
title: bad > 0 ? `${t("floatTitle")} · ${failed}` : t("floatTitle"),
|
|
1068
|
+
aria: bad > 0 ? `${t("floatAriaLabel")} · ${failed}` : `${t("floatAriaLabel")} · ${connected}/${total}`
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
function renderPill(state) {
|
|
1072
|
+
if (state.floatPill === void 0) return;
|
|
1073
|
+
const ok = state.counts.connected ?? 0;
|
|
1074
|
+
const bad = (state.counts.failed ?? 0) + (state.counts.reconnecting ?? 0);
|
|
1075
|
+
const total = state.servers.length;
|
|
1076
|
+
const fail = bad > 0;
|
|
1077
|
+
state.floatPill.classList.toggle("dm-float--fail", fail);
|
|
1078
|
+
state.floatPill.textContent = "";
|
|
1079
|
+
state.floatPill.appendChild(
|
|
1080
|
+
el("span", { class: "dm-dot", style: `background:${pillDotColor(fail, ok)}` })
|
|
1081
|
+
);
|
|
1082
|
+
if (fail) state.floatPill.appendChild(el("span", { class: "dm-float-fail", text: "!" }));
|
|
1083
|
+
state.floatPill.appendChild(el("span", { text: total > 0 ? `MCP ${ok}/${total}` : "MCP" }));
|
|
1084
|
+
if (total === 0) return;
|
|
1085
|
+
const hints = pillHints(total, bad, ok);
|
|
1086
|
+
state.floatPill.title = hints.title;
|
|
1087
|
+
state.floatPill.setAttribute("aria-label", hints.aria);
|
|
1088
|
+
}
|
|
1089
|
+
function toolCheckbox2(server, tool, disabled, state, actions) {
|
|
1090
|
+
const label = el("label", { class: "dm-float-tool" });
|
|
1091
|
+
const input = el("input", {
|
|
1092
|
+
type: "checkbox",
|
|
1093
|
+
checked: disabled,
|
|
1094
|
+
dataset: { dshMcpTool: tool }
|
|
1095
|
+
});
|
|
1096
|
+
input.addEventListener("change", () => {
|
|
1097
|
+
const serverKey = toolDisableServerKey(server, state);
|
|
1098
|
+
if (serverKey === void 0) {
|
|
1099
|
+
input.checked = !input.checked;
|
|
1100
|
+
console.warn("[dsh-mcp-manager] projectRoot 缺失,跳过 tool-disable(非法 @/name 防御)");
|
|
1101
|
+
return;
|
|
1102
|
+
}
|
|
1103
|
+
void api(state.API.toolDisable, {
|
|
1104
|
+
method: "PATCH",
|
|
1105
|
+
headers: { "content-type": "application/json" },
|
|
1106
|
+
body: JSON.stringify({
|
|
1107
|
+
server: serverKey,
|
|
1108
|
+
tool,
|
|
1109
|
+
disabled: input.checked
|
|
1110
|
+
})
|
|
1111
|
+
}).then(() => actions.refresh()).catch((error) => {
|
|
1112
|
+
input.checked = !input.checked;
|
|
1113
|
+
console.warn("[dsh-mcp-manager] tool-disable failed:", error);
|
|
1114
|
+
});
|
|
1115
|
+
});
|
|
1116
|
+
label.appendChild(input);
|
|
1117
|
+
label.appendChild(document.createTextNode(tool));
|
|
1118
|
+
return label;
|
|
1119
|
+
}
|
|
1120
|
+
function renderFloatTools(server, state, actions, openTools) {
|
|
1121
|
+
const tools = Array.isArray(server.tools) ? server.tools : [];
|
|
1122
|
+
const disabledSet = new Set(Array.isArray(server.disabledTools) ? server.disabledTools : []);
|
|
1123
|
+
const details = el("details", { class: "dm-float-tools", dataset: { dmServer: server.name } });
|
|
1124
|
+
if (openTools.has(server.name)) details.open = true;
|
|
1125
|
+
details.appendChild(el("summary", { text: t("toolsCount", { n: tools.length }) }));
|
|
1126
|
+
const list = el("div", { class: "dm-float-tool-list" });
|
|
1127
|
+
for (const tool of tools) {
|
|
1128
|
+
list.appendChild(toolCheckbox2(server, tool, disabledSet.has(tool), state, actions));
|
|
1129
|
+
}
|
|
1130
|
+
details.appendChild(list);
|
|
1131
|
+
return details;
|
|
1132
|
+
}
|
|
1133
|
+
function floatActionClass(server) {
|
|
1134
|
+
if (server.status === "failed" || server.status === "stopped")
|
|
1135
|
+
return "dm-float-action dm-primary";
|
|
1136
|
+
return "dm-float-action";
|
|
1137
|
+
}
|
|
1138
|
+
function primaryRowAction(server, state, cwdQuery) {
|
|
1139
|
+
const query = `name=${encodeURIComponent(server.name)}&scope=${server.scope}${cwdQuery}`;
|
|
1140
|
+
const className = floatActionClass(server);
|
|
1141
|
+
if (server.status === "connected") {
|
|
1142
|
+
return {
|
|
1143
|
+
className,
|
|
1144
|
+
label: t("disconnect"),
|
|
1145
|
+
url: `${state.API.disconnect}?${query}`,
|
|
1146
|
+
method: "POST",
|
|
1147
|
+
tag: "disconnect"
|
|
1148
|
+
};
|
|
1149
|
+
}
|
|
1150
|
+
if (server.status === "disabled") {
|
|
1151
|
+
return {
|
|
1152
|
+
className,
|
|
1153
|
+
label: t("enable"),
|
|
1154
|
+
url: `${state.API.servers}?${query}`,
|
|
1155
|
+
method: "PATCH",
|
|
1156
|
+
tag: "enable",
|
|
1157
|
+
body: JSON.stringify({ enabled: true })
|
|
1158
|
+
};
|
|
1159
|
+
}
|
|
1160
|
+
if (server.shadowedBy !== void 0) {
|
|
1161
|
+
return {
|
|
1162
|
+
className,
|
|
1163
|
+
label: t("connect"),
|
|
1164
|
+
url: `${state.API.sourceActivate}?${cwdQuery.replace(/^&/, "")}`,
|
|
1165
|
+
method: "POST",
|
|
1166
|
+
tag: "activate",
|
|
1167
|
+
body: JSON.stringify({ name: server.name, id: server.source })
|
|
1168
|
+
};
|
|
1169
|
+
}
|
|
1170
|
+
return {
|
|
1171
|
+
className,
|
|
1172
|
+
label: t("connect"),
|
|
1173
|
+
url: `${state.API.connect}?${query}`,
|
|
1174
|
+
method: "POST",
|
|
1175
|
+
tag: "connect"
|
|
1176
|
+
};
|
|
1177
|
+
}
|
|
1178
|
+
function disableRowAction(server, state) {
|
|
1179
|
+
return {
|
|
1180
|
+
className: "dm-float-action",
|
|
1181
|
+
label: t("disable"),
|
|
1182
|
+
url: `${state.API.servers}?name=${encodeURIComponent(server.name)}&scope=${server.scope}`,
|
|
1183
|
+
method: "PATCH",
|
|
1184
|
+
tag: "disable",
|
|
1185
|
+
body: JSON.stringify({ enabled: false })
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
function rowRequestInit(action) {
|
|
1189
|
+
if (action.body === void 0) return { method: action.method };
|
|
1190
|
+
return {
|
|
1191
|
+
method: action.method,
|
|
1192
|
+
headers: { "content-type": "application/json" },
|
|
1193
|
+
body: action.body
|
|
1194
|
+
};
|
|
1195
|
+
}
|
|
1196
|
+
function rowActionButton(action, actions) {
|
|
1197
|
+
const button = el("button", { class: action.className });
|
|
1198
|
+
button.textContent = action.label;
|
|
1199
|
+
button.addEventListener("click", () => {
|
|
1200
|
+
const original = button.textContent ?? "";
|
|
1201
|
+
focusGuardSuppressUntil = Date.now() + 800;
|
|
1202
|
+
button.disabled = true;
|
|
1203
|
+
button.style.opacity = "0.6";
|
|
1204
|
+
button.textContent = `${original}…`;
|
|
1205
|
+
const done = () => {
|
|
1206
|
+
button.disabled = false;
|
|
1207
|
+
button.style.opacity = "";
|
|
1208
|
+
button.textContent = original;
|
|
1209
|
+
};
|
|
1210
|
+
void api(action.url, rowRequestInit(action)).then(() => actions.refresh()).catch((error) => {
|
|
1211
|
+
console.warn(`[dsh-mcp-manager] ${action.tag} failed:`, error);
|
|
1212
|
+
window.alert(`${action.tag} failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1213
|
+
}).finally(done);
|
|
1214
|
+
});
|
|
1215
|
+
return button;
|
|
1216
|
+
}
|
|
1217
|
+
function renderFloatRow(server, state, actions, opts = { tools: true }) {
|
|
1218
|
+
const failed = server.status === "failed" || server.status === "reconnecting";
|
|
1219
|
+
const row = el("div", {
|
|
1220
|
+
class: `dm-float-row${failed ? " dm-float-row--fail" : ""}${opts.stagger !== void 0 ? " dm-stagger" : ""}`
|
|
1221
|
+
});
|
|
1222
|
+
if (opts.stagger !== void 0) {
|
|
1223
|
+
row.style.animationDelay = `${Math.min(opts.stagger, 5) * 30}ms`;
|
|
1224
|
+
}
|
|
1225
|
+
row.appendChild(el("span", { class: "dm-dot", style: `background:${statusDot(server.status)}` }));
|
|
1226
|
+
row.appendChild(el("span", { class: "dm-float-name", text: server.name, title: server.name }));
|
|
1227
|
+
const tools = Array.isArray(server.tools) ? server.tools.length : 0;
|
|
1228
|
+
row.appendChild(
|
|
1229
|
+
el("span", {
|
|
1230
|
+
class: "dm-float-meta",
|
|
1231
|
+
text: pangu(t("serverMeta", { status: tStatus(server.status), tools }))
|
|
1232
|
+
})
|
|
1233
|
+
);
|
|
1234
|
+
const actionsEl = el("div", { class: "dm-float-actions" });
|
|
1235
|
+
actionsEl.appendChild(
|
|
1236
|
+
rowActionButton(primaryRowAction(server, state, cwdQueryOf(state)), actions)
|
|
1237
|
+
);
|
|
1238
|
+
if (server.status !== "disabled") {
|
|
1239
|
+
actionsEl.appendChild(rowActionButton(disableRowAction(server, state), actions));
|
|
1240
|
+
}
|
|
1241
|
+
row.appendChild(actionsEl);
|
|
1242
|
+
if (opts.tools)
|
|
1243
|
+
row.appendChild(renderFloatTools(server, state, actions, opts.openTools ?? /* @__PURE__ */ new Set()));
|
|
1244
|
+
return row;
|
|
1245
|
+
}
|
|
1246
|
+
function renderFloatHealth(state) {
|
|
1247
|
+
const connected = state.counts.connected ?? 0;
|
|
1248
|
+
const connecting = (state.counts.connecting ?? 0) + (state.counts.reconnecting ?? 0);
|
|
1249
|
+
const failed = state.counts.failed ?? 0;
|
|
1250
|
+
const stopped = state.counts.stopped ?? 0;
|
|
1251
|
+
const parts = [];
|
|
1252
|
+
parts.push(
|
|
1253
|
+
el("span", { class: "dm-health-ok", text: pangu(t("healthRunning", { n: connected })) })
|
|
1254
|
+
);
|
|
1255
|
+
if (connecting > 0)
|
|
1256
|
+
parts.push(document.createTextNode(pangu(t("healthConnecting", { n: connecting }))));
|
|
1257
|
+
if (stopped > 0) parts.push(document.createTextNode(pangu(t("healthStopped", { n: stopped }))));
|
|
1258
|
+
if (failed > 0)
|
|
1259
|
+
parts.push(
|
|
1260
|
+
el("span", { class: "dm-health-bad", text: pangu(t("healthFailed", { n: failed })) })
|
|
1261
|
+
);
|
|
1262
|
+
const line = el("div", { class: "dm-float-health" });
|
|
1263
|
+
parts.forEach((part, index) => {
|
|
1264
|
+
if (index > 0) line.appendChild(document.createTextNode(" · "));
|
|
1265
|
+
line.appendChild(part);
|
|
1266
|
+
});
|
|
1267
|
+
return line;
|
|
1268
|
+
}
|
|
1269
|
+
function bucketServersByStatus(list) {
|
|
1270
|
+
const byStatus = /* @__PURE__ */ new Map();
|
|
1271
|
+
for (const group of STATUS_ORDER) byStatus.set(group.key, []);
|
|
1272
|
+
for (const server of list) {
|
|
1273
|
+
const bucket = byStatus.get(server.status) ?? byStatus.get("stopped");
|
|
1274
|
+
if (bucket !== void 0) bucket.push(server);
|
|
1275
|
+
}
|
|
1276
|
+
return byStatus;
|
|
1277
|
+
}
|
|
1278
|
+
function nextStagger(staggerBase, animate) {
|
|
1279
|
+
if (!animate) return void 0;
|
|
1280
|
+
const current = staggerBase.n;
|
|
1281
|
+
staggerBase.n += 1;
|
|
1282
|
+
return current;
|
|
1283
|
+
}
|
|
1284
|
+
function appendScopeGroup(panel, scope, list, state, actions, openTools, staggerBase, animateRows, titleOverride, alert) {
|
|
1285
|
+
const section = el("section", {
|
|
1286
|
+
class: "dm-float-group",
|
|
1287
|
+
style: "border:1px solid var(--dsw-alias-border-l2, #e2e4ea);border-radius:14px;padding:10px 12px;margin:0 0 12px;"
|
|
1288
|
+
});
|
|
1289
|
+
section.appendChild(
|
|
1290
|
+
el("div", {
|
|
1291
|
+
class: `dm-float-group-title${alert === true ? " dm-float-group-title--alert" : ""}`,
|
|
1292
|
+
text: titleOverride ?? (scope === "project" ? t("groupProject") : t("groupGlobal"))
|
|
1293
|
+
})
|
|
1294
|
+
);
|
|
1295
|
+
if (list.length === 0) {
|
|
1296
|
+
const emptyItem = el("div", {
|
|
1297
|
+
class: "dm-float-row",
|
|
1298
|
+
style: "cursor:pointer;color:var(--dsw-alias-state-business-primary, #2f6fe0);padding:8px 4px 2px;",
|
|
1299
|
+
text: "+ " + t("srcQuickSetup")
|
|
1300
|
+
});
|
|
1301
|
+
emptyItem.addEventListener("click", () => {
|
|
1302
|
+
openMainPanel(state, actions);
|
|
1303
|
+
actions.switchTab("quick");
|
|
1304
|
+
requestAnimationFrame(() => {
|
|
1305
|
+
const doc = state.bodyEl?.ownerDocument ?? document;
|
|
1306
|
+
if (scope === "global-dsh" || scope === "global-agents" || scope === "project-dsh" || scope === "project-agents") {
|
|
1307
|
+
doc.querySelector(`[data-dm-src="${scope}"]`)?.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
1308
|
+
}
|
|
1309
|
+
});
|
|
1310
|
+
});
|
|
1311
|
+
section.appendChild(emptyItem);
|
|
1312
|
+
panel.appendChild(section);
|
|
1313
|
+
return;
|
|
1314
|
+
}
|
|
1315
|
+
const byStatus = bucketServersByStatus(list);
|
|
1316
|
+
for (const group of STATUS_ORDER) {
|
|
1317
|
+
const bucket = byStatus.get(group.key) ?? [];
|
|
1318
|
+
if (bucket.length === 0) continue;
|
|
1319
|
+
const ordered = [...bucket].sort(
|
|
1320
|
+
(a, b) => a.name.localeCompare(b.name)
|
|
1321
|
+
);
|
|
1322
|
+
for (const server of ordered) {
|
|
1323
|
+
section.appendChild(
|
|
1324
|
+
renderFloatRow(server, state, actions, {
|
|
1325
|
+
tools: true,
|
|
1326
|
+
openTools,
|
|
1327
|
+
stagger: nextStagger(staggerBase, animateRows)
|
|
1328
|
+
})
|
|
1329
|
+
);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
panel.appendChild(section);
|
|
1333
|
+
}
|
|
1334
|
+
function floatProjectName(state) {
|
|
1335
|
+
const root = state.projectRoot;
|
|
1336
|
+
if (typeof root !== "string" || root === "") return t("floatGlobalSession");
|
|
1337
|
+
return root.split(/[\\/]/).filter(Boolean).pop() ?? root;
|
|
1338
|
+
}
|
|
1339
|
+
function openMainPanel(state, actions) {
|
|
1340
|
+
toggleFloat(state, actions, false);
|
|
1341
|
+
actions.showPanel();
|
|
1342
|
+
}
|
|
1343
|
+
function renderFloatEmpty(state, actions) {
|
|
1344
|
+
const empty = el("div", { class: "dm-status" });
|
|
1345
|
+
empty.appendChild(el("div", { text: t("floatEmptyTitle") }));
|
|
1346
|
+
empty.appendChild(
|
|
1347
|
+
el("button", {
|
|
1348
|
+
class: "dm-float-action dm-primary",
|
|
1349
|
+
text: t("floatEmptyCta"),
|
|
1350
|
+
style: "margin-top:12px",
|
|
1351
|
+
onclick: () => openMainPanel(state, actions)
|
|
1352
|
+
})
|
|
1353
|
+
);
|
|
1354
|
+
return empty;
|
|
1355
|
+
}
|
|
1356
|
+
function needsAttention2(server) {
|
|
1357
|
+
return server.status === "failed" || server.status === "reconnecting";
|
|
1358
|
+
}
|
|
1359
|
+
var focusGuardSuppressUntil = 0;
|
|
1360
|
+
function renderFloatPanel(state, actions) {
|
|
1361
|
+
if (state.floatPanel === void 0) return;
|
|
1362
|
+
const hadFocusInside = state.floatPanel.contains(document.activeElement);
|
|
1363
|
+
if (hadFocusInside) focusGuardSuppressUntil = Date.now() + 300;
|
|
1364
|
+
const openTools = /* @__PURE__ */ new Set();
|
|
1365
|
+
for (const d of state.floatPanel.querySelectorAll("details.dm-float-tools")) {
|
|
1366
|
+
if (d.open && d.dataset.dmServer !== void 0) openTools.add(d.dataset.dmServer);
|
|
1367
|
+
}
|
|
1368
|
+
state.floatPanel.textContent = "";
|
|
1369
|
+
const head = el("div", { class: "dm-float-head" });
|
|
1370
|
+
const headText = el("div", { class: "dm-float-head-text" });
|
|
1371
|
+
headText.appendChild(el("div", { class: "dm-float-title", text: floatProjectName(state) }));
|
|
1372
|
+
headText.appendChild(renderFloatHealth(state));
|
|
1373
|
+
head.appendChild(headText);
|
|
1374
|
+
head.appendChild(
|
|
1375
|
+
el("button", { text: t("floatManage"), onclick: () => openMainPanel(state, actions) })
|
|
1376
|
+
);
|
|
1377
|
+
state.floatPanel.appendChild(head);
|
|
1378
|
+
if (state.servers.length === 0) {
|
|
1379
|
+
state.floatPanel.appendChild(renderFloatEmpty(state, actions));
|
|
1380
|
+
if (state.floatOpen) placePanel(state);
|
|
1381
|
+
return;
|
|
1382
|
+
}
|
|
1383
|
+
const animateRows = state.floatPanel.dataset.dmStagger === "1";
|
|
1384
|
+
const staggerBase = { n: 0 };
|
|
1385
|
+
const attention = state.servers.filter(needsAttention2);
|
|
1386
|
+
void attention;
|
|
1387
|
+
const rest = state.servers;
|
|
1388
|
+
const FLOAT_GROUPS = [
|
|
1389
|
+
{ id: "global-dsh", labelKey: "srcGlobalDsh" },
|
|
1390
|
+
{ id: "global-agents", labelKey: "srcGlobalAgents" },
|
|
1391
|
+
{ id: "project-dsh", labelKey: "srcProjectDsh" },
|
|
1392
|
+
{ id: "project-agents", labelKey: "srcProjectAgents" }
|
|
1393
|
+
];
|
|
1394
|
+
const pick = (id) => rest.filter((server) => server.source === id);
|
|
1395
|
+
for (const group of FLOAT_GROUPS) {
|
|
1396
|
+
const list = pick(group.id);
|
|
1397
|
+
appendScopeGroup(
|
|
1398
|
+
state.floatPanel,
|
|
1399
|
+
group.id,
|
|
1400
|
+
list,
|
|
1401
|
+
state,
|
|
1402
|
+
actions,
|
|
1403
|
+
openTools,
|
|
1404
|
+
staggerBase,
|
|
1405
|
+
animateRows,
|
|
1406
|
+
`${t(group.labelKey)} (${list.length})`
|
|
1407
|
+
);
|
|
1408
|
+
}
|
|
1409
|
+
const floatUngrouped = rest.filter(
|
|
1410
|
+
(server) => !FLOAT_GROUPS.some((g) => g.id === server.source)
|
|
1411
|
+
);
|
|
1412
|
+
if (floatUngrouped.length > 0) {
|
|
1413
|
+
appendScopeGroup(
|
|
1414
|
+
state.floatPanel,
|
|
1415
|
+
"other",
|
|
1416
|
+
floatUngrouped,
|
|
1417
|
+
state,
|
|
1418
|
+
actions,
|
|
1419
|
+
openTools,
|
|
1420
|
+
staggerBase,
|
|
1421
|
+
animateRows,
|
|
1422
|
+
`${t("srcUngrouped")} (${floatUngrouped.length})`
|
|
1423
|
+
);
|
|
1424
|
+
}
|
|
1425
|
+
void attention;
|
|
1426
|
+
delete state.floatPanel.dataset.dmStagger;
|
|
1427
|
+
if (hadFocusInside) {
|
|
1428
|
+
state.floatPanel.focus({ preventScroll: true });
|
|
1429
|
+
}
|
|
1430
|
+
if (state.floatOpen) placePanel(state);
|
|
1431
|
+
}
|
|
1432
|
+
function toggleFloat(state, actions, force) {
|
|
1433
|
+
if (state.floatPanel === void 0) return;
|
|
1434
|
+
const next = force !== void 0 ? force : !state.floatOpen;
|
|
1435
|
+
state.floatOpen = next;
|
|
1436
|
+
const panel = state.floatPanel;
|
|
1437
|
+
const pill = state.floatPill;
|
|
1438
|
+
if (next) {
|
|
1439
|
+
panel.dataset.dmStagger = "1";
|
|
1440
|
+
panel.hidden = false;
|
|
1441
|
+
panel.classList.remove("dm-float-panel--closing", "dm-float-panel--open");
|
|
1442
|
+
renderFloatPanel(state, actions);
|
|
1443
|
+
placePanel(state);
|
|
1444
|
+
const armOpen = () => {
|
|
1445
|
+
if (!state.floatOpen) return;
|
|
1446
|
+
replayAnim(panel);
|
|
1447
|
+
panel.classList.add("dm-float-panel--open");
|
|
1448
|
+
pill?.classList.add("dm-float--open");
|
|
1449
|
+
};
|
|
1450
|
+
if (typeof requestAnimationFrame === "function") requestAnimationFrame(armOpen);
|
|
1451
|
+
else armOpen();
|
|
1452
|
+
panel.focus({ preventScroll: true });
|
|
1453
|
+
return;
|
|
1454
|
+
}
|
|
1455
|
+
pill?.classList.remove("dm-float--open");
|
|
1456
|
+
panel.classList.remove("dm-float-panel--open");
|
|
1457
|
+
const finish = () => {
|
|
1458
|
+
if (state.floatOpen) return;
|
|
1459
|
+
panel.classList.remove("dm-float-panel--closing");
|
|
1460
|
+
panel.hidden = true;
|
|
1461
|
+
};
|
|
1462
|
+
if (typeof requestAnimationFrame === "function") {
|
|
1463
|
+
replayAnim(panel);
|
|
1464
|
+
panel.classList.add("dm-float-panel--closing");
|
|
1465
|
+
window.setTimeout(finish, 300);
|
|
1466
|
+
} else {
|
|
1467
|
+
finish();
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
function anchorsLeft(position) {
|
|
1471
|
+
return position === "top-left" || position === "bottom-left";
|
|
1472
|
+
}
|
|
1473
|
+
function transformOriginFor(isLeft, anchorBottom) {
|
|
1474
|
+
if (anchorBottom) return isLeft ? "bottom left" : "bottom right";
|
|
1475
|
+
return isLeft ? "top left" : "top right";
|
|
1476
|
+
}
|
|
1477
|
+
function placePanel(state) {
|
|
1478
|
+
if (state.floatPill === void 0 || state.floatPanel === void 0) return;
|
|
1479
|
+
const pillRect = state.floatPill.getBoundingClientRect();
|
|
1480
|
+
if (pillRect.width === 0 && pillRect.height === 0) return;
|
|
1481
|
+
const panel = state.floatPanel;
|
|
1482
|
+
const position = state.mcpUiConfig?.position;
|
|
1483
|
+
const anchorBottom = panelAnchorForPosition(position) === "bottom";
|
|
1484
|
+
const isLeft = anchorsLeft(position);
|
|
1485
|
+
const gap = 6;
|
|
1486
|
+
const rawTop = anchorBottom ? Math.max(6, pillRect.top - panel.offsetHeight - gap) : Math.max(6, pillRect.bottom + gap);
|
|
1487
|
+
const rawLeft = isLeft ? Math.max(10, Math.round(pillRect.left)) : Math.max(10, Math.round(pillRect.right - panel.offsetWidth));
|
|
1488
|
+
const point = clampPointToViewport(
|
|
1489
|
+
rawLeft,
|
|
1490
|
+
rawTop,
|
|
1491
|
+
panel.offsetWidth,
|
|
1492
|
+
panel.offsetHeight,
|
|
1493
|
+
window.innerWidth,
|
|
1494
|
+
window.innerHeight
|
|
1495
|
+
);
|
|
1496
|
+
panel.style.left = `${Math.round(point.x)}px`;
|
|
1497
|
+
panel.style.top = `${Math.round(point.y)}px`;
|
|
1498
|
+
panel.style.right = "auto";
|
|
1499
|
+
panel.style.transformOrigin = transformOriginFor(isLeft, anchorBottom);
|
|
1500
|
+
}
|
|
1501
|
+
function conversationHost() {
|
|
1502
|
+
return document.querySelector("[data-conversation-scroll]") ?? document.querySelector('[data-pane="conversation"]') ?? document.querySelector(".pI_x6G_centerCol") ?? document.body;
|
|
1503
|
+
}
|
|
1504
|
+
function panelHost() {
|
|
1505
|
+
return document.querySelector("[data-shell-overlay]") ?? document.body;
|
|
1506
|
+
}
|
|
1507
|
+
function isBlankSession(ctx) {
|
|
1508
|
+
const read = readCurrentSession(ctx?.sessions?.list);
|
|
1509
|
+
return read.kind === "session" && read.blank;
|
|
1510
|
+
}
|
|
1511
|
+
function floatTopOffset(ctx, state) {
|
|
1512
|
+
const cfg = state.mcpUiConfig || {};
|
|
1513
|
+
const y = typeof cfg.offsetY === "number" ? cfg.offsetY : 8;
|
|
1514
|
+
const blankY = typeof cfg.blankY === "number" ? cfg.blankY : y;
|
|
1515
|
+
return isBlankSession(ctx) ? blankY : y;
|
|
1516
|
+
}
|
|
1517
|
+
function floatPositionOf(cfg) {
|
|
1518
|
+
const position = cfg.position;
|
|
1519
|
+
if (position === "top-left" || position === "bottom-right" || position === "bottom-left")
|
|
1520
|
+
return position;
|
|
1521
|
+
return "top-right";
|
|
1522
|
+
}
|
|
1523
|
+
function pillAnchors(cfg, ctx, state) {
|
|
1524
|
+
const position = floatPositionOf(cfg);
|
|
1525
|
+
return {
|
|
1526
|
+
isLeft: anchorsLeft(position),
|
|
1527
|
+
isBottom: panelAnchorForPosition(position) === "bottom",
|
|
1528
|
+
offsetX: typeof cfg.offsetX === "number" ? cfg.offsetX : 8,
|
|
1529
|
+
offsetY: floatTopOffset(ctx, state)
|
|
1530
|
+
};
|
|
1531
|
+
}
|
|
1532
|
+
function usesComposerSeat(isBottom, bp) {
|
|
1533
|
+
return isBottom && bp !== "wide";
|
|
1534
|
+
}
|
|
1535
|
+
function dockedBottomEdge(containerRect) {
|
|
1536
|
+
const seat = document.querySelector("[data-composer-seat]");
|
|
1537
|
+
const seatRect = seat !== null ? seat.getBoundingClientRect() : null;
|
|
1538
|
+
return bottomAnchorEdge(
|
|
1539
|
+
containerRect.bottom,
|
|
1540
|
+
seatRect?.top ?? null,
|
|
1541
|
+
composerDockedAtBottom(seatRect, containerRect)
|
|
1542
|
+
);
|
|
1543
|
+
}
|
|
1544
|
+
function pillPlacement(input) {
|
|
1545
|
+
const rawLeft = input.isLeft ? input.rect.left + input.offsetX : input.rect.right - input.width - input.offsetX;
|
|
1546
|
+
const rawTop = input.isBottom ? Math.max(6, input.bottomEdge - input.height - input.offsetY) : input.rect.top + input.offsetY;
|
|
1547
|
+
return clampPointToViewport(
|
|
1548
|
+
rawLeft,
|
|
1549
|
+
rawTop,
|
|
1550
|
+
input.width,
|
|
1551
|
+
input.height,
|
|
1552
|
+
input.viewportW,
|
|
1553
|
+
input.viewportH
|
|
1554
|
+
);
|
|
1555
|
+
}
|
|
1556
|
+
function mountFloat(ctx, state, actions) {
|
|
1557
|
+
const pill = el("button", {
|
|
1558
|
+
type: "button",
|
|
1559
|
+
class: "dm-float",
|
|
1560
|
+
"aria-label": t("floatAriaLabel"),
|
|
1561
|
+
title: t("floatTitle")
|
|
1562
|
+
});
|
|
1563
|
+
pill.dataset.dshMcpFloat = "";
|
|
1564
|
+
pill.addEventListener("click", () => toggleFloat(state, actions));
|
|
1565
|
+
const panel = el("div", { class: "dm-float-panel", role: "dialog" });
|
|
1566
|
+
panel.hidden = true;
|
|
1567
|
+
panel.tabIndex = -1;
|
|
1568
|
+
state.floatPill = pill;
|
|
1569
|
+
state.floatPanel = panel;
|
|
1570
|
+
const panelRoot = panelHost();
|
|
1571
|
+
if (panel.parentElement !== panelRoot) panelRoot.appendChild(panel);
|
|
1572
|
+
const onFocusOut = (event) => {
|
|
1573
|
+
if (!state.floatOpen) return;
|
|
1574
|
+
if (Date.now() < focusGuardSuppressUntil) return;
|
|
1575
|
+
const next = event.relatedTarget;
|
|
1576
|
+
if (next !== null && (state.floatPanel?.contains(next) || state.floatPill?.contains(next)))
|
|
1577
|
+
return;
|
|
1578
|
+
toggleFloat(state, actions, false);
|
|
1579
|
+
};
|
|
1580
|
+
document.addEventListener("focusout", onFocusOut);
|
|
1581
|
+
const onKeyDown = (event) => {
|
|
1582
|
+
if (event.defaultPrevented) return;
|
|
1583
|
+
if (event.key === "Escape" && state.floatOpen) toggleFloat(state, actions, false);
|
|
1584
|
+
};
|
|
1585
|
+
document.addEventListener("keydown", onKeyDown);
|
|
1586
|
+
let host;
|
|
1587
|
+
const updateFloat = () => {
|
|
1588
|
+
const best = conversationHost();
|
|
1589
|
+
if (best === null || best === void 0) return;
|
|
1590
|
+
const rect = best.getBoundingClientRect();
|
|
1591
|
+
const cfg = state.mcpUiConfig || {};
|
|
1592
|
+
const bp = breakpointForWidth(rect.width);
|
|
1593
|
+
if (pill.dataset.dmBp !== bp) pill.dataset.dmBp = bp;
|
|
1594
|
+
if (panel.dataset.dmBp !== bp) panel.dataset.dmBp = bp;
|
|
1595
|
+
const zBase = clampZIndexBase(cfg.zIndexBase, DEFAULT_Z_INDEX_BASE);
|
|
1596
|
+
pill.style.zIndex = String(zBase);
|
|
1597
|
+
panel.style.zIndex = String(zBase);
|
|
1598
|
+
const anchors = pillAnchors(cfg, ctx, state);
|
|
1599
|
+
pill.style.position = "fixed";
|
|
1600
|
+
const bottomEdge = usesComposerSeat(anchors.isBottom, bp) ? dockedBottomEdge(rect) : rect.bottom;
|
|
1601
|
+
const point = pillPlacement({
|
|
1602
|
+
rect,
|
|
1603
|
+
width: pill.offsetWidth,
|
|
1604
|
+
height: pill.offsetHeight,
|
|
1605
|
+
...anchors,
|
|
1606
|
+
bottomEdge,
|
|
1607
|
+
viewportW: window.innerWidth,
|
|
1608
|
+
viewportH: window.innerHeight
|
|
1609
|
+
});
|
|
1610
|
+
pill.style.left = `${Math.round(point.x)}px`;
|
|
1611
|
+
pill.style.top = `${Math.round(point.y)}px`;
|
|
1612
|
+
pill.style.right = "auto";
|
|
1613
|
+
if (state.floatOpen) placePanel(state);
|
|
1614
|
+
};
|
|
1615
|
+
state.updateFloatState = updateFloat;
|
|
1616
|
+
const listeners = [];
|
|
1617
|
+
let rafId = 0;
|
|
1618
|
+
const scheduleUpdate = () => {
|
|
1619
|
+
if (rafId !== 0) return;
|
|
1620
|
+
rafId = requestAnimationFrame(() => {
|
|
1621
|
+
rafId = 0;
|
|
1622
|
+
updateFloat();
|
|
1623
|
+
});
|
|
1624
|
+
};
|
|
1625
|
+
const onOrientationChange = () => {
|
|
1626
|
+
scheduleUpdate();
|
|
1627
|
+
};
|
|
1628
|
+
const onVisualViewportResize = () => {
|
|
1629
|
+
scheduleUpdate();
|
|
1630
|
+
};
|
|
1631
|
+
const attachListeners = (target) => {
|
|
1632
|
+
for (const detach of listeners.splice(0)) detach();
|
|
1633
|
+
target.addEventListener("scroll", scheduleUpdate, { passive: true });
|
|
1634
|
+
window.addEventListener("resize", scheduleUpdate);
|
|
1635
|
+
window.addEventListener("orientationchange", onOrientationChange);
|
|
1636
|
+
window.visualViewport?.addEventListener("resize", onVisualViewportResize);
|
|
1637
|
+
listeners.push(() => {
|
|
1638
|
+
target.removeEventListener("scroll", scheduleUpdate);
|
|
1639
|
+
window.removeEventListener("resize", scheduleUpdate);
|
|
1640
|
+
window.removeEventListener("orientationchange", onOrientationChange);
|
|
1641
|
+
window.visualViewport?.removeEventListener("resize", onVisualViewportResize);
|
|
1642
|
+
});
|
|
1643
|
+
updateFloat();
|
|
1644
|
+
};
|
|
1645
|
+
const place = () => {
|
|
1646
|
+
const best = conversationHost();
|
|
1647
|
+
if (best === null || best === void 0) return false;
|
|
1648
|
+
if (host !== best || pill.parentElement !== best) {
|
|
1649
|
+
host = best;
|
|
1650
|
+
if (pill.parentElement !== null) pill.remove();
|
|
1651
|
+
best.appendChild(pill);
|
|
1652
|
+
attachListeners(best);
|
|
1653
|
+
}
|
|
1654
|
+
return true;
|
|
1655
|
+
};
|
|
1656
|
+
let observerRafId = 0;
|
|
1657
|
+
const schedulePlace = () => {
|
|
1658
|
+
if (observerRafId !== 0) return;
|
|
1659
|
+
observerRafId = requestAnimationFrame(() => {
|
|
1660
|
+
observerRafId = 0;
|
|
1661
|
+
place();
|
|
1662
|
+
});
|
|
1663
|
+
};
|
|
1664
|
+
const observer = new MutationObserver(schedulePlace);
|
|
1665
|
+
if (place()) {
|
|
1666
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
1667
|
+
} else {
|
|
1668
|
+
const wait = new MutationObserver(() => {
|
|
1669
|
+
if (place()) {
|
|
1670
|
+
wait.disconnect();
|
|
1671
|
+
observer.observe(document.body, { childList: true, subtree: true });
|
|
1672
|
+
}
|
|
1673
|
+
});
|
|
1674
|
+
wait.observe(document.body, { childList: true, subtree: true });
|
|
1675
|
+
}
|
|
1676
|
+
renderPill(state);
|
|
1677
|
+
return () => {
|
|
1678
|
+
state.updateFloatState = void 0;
|
|
1679
|
+
document.removeEventListener("focusout", onFocusOut);
|
|
1680
|
+
document.removeEventListener("keydown", onKeyDown);
|
|
1681
|
+
observer.disconnect();
|
|
1682
|
+
for (const detach of listeners.splice(0)) detach();
|
|
1683
|
+
if (rafId !== 0) {
|
|
1684
|
+
cancelAnimationFrame(rafId);
|
|
1685
|
+
rafId = 0;
|
|
1686
|
+
}
|
|
1687
|
+
if (observerRafId !== 0) {
|
|
1688
|
+
cancelAnimationFrame(observerRafId);
|
|
1689
|
+
observerRafId = 0;
|
|
1690
|
+
}
|
|
1691
|
+
pill.remove();
|
|
1692
|
+
panel.remove();
|
|
1693
|
+
state.floatPill = void 0;
|
|
1694
|
+
state.floatPanel = void 0;
|
|
1695
|
+
state.floatOpen = false;
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1699
|
+
// src/client/float/panel.ts
|
|
1700
|
+
var inflight;
|
|
1701
|
+
var keydownCleanups = /* @__PURE__ */ new WeakMap();
|
|
1702
|
+
function refresh(state, actions) {
|
|
1703
|
+
if (inflight !== void 0) return inflight;
|
|
1704
|
+
inflight = doRefresh(state, actions).finally(() => {
|
|
1705
|
+
inflight = void 0;
|
|
1706
|
+
});
|
|
1707
|
+
return inflight;
|
|
1708
|
+
}
|
|
1709
|
+
function countsSummaryText(state) {
|
|
1710
|
+
const parts = [];
|
|
1711
|
+
const { connected = 0, connecting = 0, reconnecting = 0, failed = 0 } = state.counts;
|
|
1712
|
+
if (connected > 0) parts.push(t("countsConnected", { n: connected }));
|
|
1713
|
+
if (connecting > 0 || reconnecting > 0)
|
|
1714
|
+
parts.push(t("countsConnecting", { n: connecting + reconnecting }));
|
|
1715
|
+
if (failed > 0) parts.push(t("countsFailed", { n: failed }));
|
|
1716
|
+
return parts.length > 0 ? t("countsSummary", { n: state.servers.length, parts: parts.join(" · ") }) : t("countsSummaryOnly", { n: state.servers.length });
|
|
1717
|
+
}
|
|
1718
|
+
function renderCountsBadge(state) {
|
|
1719
|
+
const countsEl = document.querySelector(".dm-counts");
|
|
1720
|
+
if (countsEl === null) return;
|
|
1721
|
+
const summary = countsSummaryText(state);
|
|
1722
|
+
const failed = state.counts.failed ?? 0;
|
|
1723
|
+
if (failed > 0 && typeof summary === "string") {
|
|
1724
|
+
const failedText = t("countsFailed", { n: failed });
|
|
1725
|
+
countsEl.textContent = "";
|
|
1726
|
+
summary.split(failedText).forEach((chunk, index) => {
|
|
1727
|
+
if (index > 0) {
|
|
1728
|
+
countsEl.appendChild(el("span", { class: "dm-health-bad", text: failedText }));
|
|
1729
|
+
}
|
|
1730
|
+
if (chunk !== "") countsEl.appendChild(document.createTextNode(chunk));
|
|
1731
|
+
});
|
|
1732
|
+
return;
|
|
1733
|
+
}
|
|
1734
|
+
countsEl.textContent = summary;
|
|
1735
|
+
}
|
|
1736
|
+
async function doRefresh(state, actions) {
|
|
1737
|
+
try {
|
|
1738
|
+
const payload = await api(state.API.servers);
|
|
1739
|
+
state.servers = payload.servers ?? [];
|
|
1740
|
+
state.counts = payload.counts ?? {};
|
|
1741
|
+
state.projectRoot = payload.projectRoot;
|
|
1742
|
+
renderPill(state);
|
|
1743
|
+
if (state.floatOpen) renderFloatPanel(state, actions);
|
|
1744
|
+
renderCountsBadge(state);
|
|
1745
|
+
if (state.bodyEl !== void 0 && state.activeTab === "servers") renderServers(state, actions);
|
|
1746
|
+
return true;
|
|
1747
|
+
} catch (error) {
|
|
1748
|
+
if (state.bodyEl !== void 0 && state.activeTab === "servers") {
|
|
1749
|
+
state.bodyEl.textContent = "";
|
|
1750
|
+
state.bodyEl.appendChild(
|
|
1751
|
+
el("div", {
|
|
1752
|
+
class: "dm-status",
|
|
1753
|
+
text: t("loadFail", { msg: error instanceof Error ? error.message : String(error) })
|
|
1754
|
+
})
|
|
1755
|
+
);
|
|
1756
|
+
}
|
|
1757
|
+
return false;
|
|
1758
|
+
}
|
|
1759
|
+
}
|
|
1760
|
+
function switchTab(state, actions, tab) {
|
|
1761
|
+
state.activeTab = tab;
|
|
1762
|
+
for (const tabEl of document.querySelectorAll(".dm-tab")) {
|
|
1763
|
+
tabEl.dataset.active = tabEl.dataset.tab === tab ? "true" : "";
|
|
1764
|
+
}
|
|
1765
|
+
if (state.bodyEl === void 0) return;
|
|
1766
|
+
state.bodyEl.textContent = "";
|
|
1767
|
+
if (tab === "servers") {
|
|
1768
|
+
if (state.servers.length === 0) void refresh(state, actions);
|
|
1769
|
+
else renderServers(state, actions);
|
|
1770
|
+
} else {
|
|
1771
|
+
const page = buildQuickAdd(state, actions);
|
|
1772
|
+
state.bodyEl.appendChild(page);
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
function close(state) {
|
|
1776
|
+
if (state.overlay === void 0) return;
|
|
1777
|
+
state.open = false;
|
|
1778
|
+
if (state.card !== void 0) state.card.removeAttribute("aria-modal");
|
|
1779
|
+
const opener = state.panelOpener;
|
|
1780
|
+
state.panelOpener = void 0;
|
|
1781
|
+
if (opener !== void 0 && opener !== null && typeof opener.focus === "function") {
|
|
1782
|
+
try {
|
|
1783
|
+
if (typeof opener.isConnected !== "boolean" || opener.isConnected) {
|
|
1784
|
+
opener.focus({ preventScroll: true });
|
|
1785
|
+
}
|
|
1786
|
+
} catch {
|
|
1787
|
+
console.warn("[dsh-mcp-manager] panel opener focus restore skipped");
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
const overlay = state.overlay;
|
|
1791
|
+
overlay.classList.remove("dm-overlay--open");
|
|
1792
|
+
const finish = () => {
|
|
1793
|
+
if (state.open) return;
|
|
1794
|
+
overlay.classList.remove("dm-overlay--closing");
|
|
1795
|
+
overlay.hidden = true;
|
|
1796
|
+
};
|
|
1797
|
+
if (typeof requestAnimationFrame === "function") {
|
|
1798
|
+
overlay.style.animation = "none";
|
|
1799
|
+
void overlay.offsetWidth;
|
|
1800
|
+
overlay.style.animation = "";
|
|
1801
|
+
overlay.classList.add("dm-overlay--closing");
|
|
1802
|
+
window.setTimeout(finish, 300);
|
|
1803
|
+
} else {
|
|
1804
|
+
finish();
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
function showPanel(state, actions) {
|
|
1808
|
+
if (state.overlay === void 0) {
|
|
1809
|
+
state.overlay = el("div", { class: "dm-overlay", hidden: true });
|
|
1810
|
+
state.overlay.addEventListener("click", (event) => {
|
|
1811
|
+
if (event.target === state.overlay) close(state);
|
|
1812
|
+
});
|
|
1813
|
+
state.card = el("div", { class: "dm-card" });
|
|
1814
|
+
const head = el("div", { class: "dm-head" });
|
|
1815
|
+
head.appendChild(el("h2", { text: t("panelTitle") }));
|
|
1816
|
+
head.appendChild(el("span", { class: "dm-counts", text: "" }));
|
|
1817
|
+
head.appendChild(
|
|
1818
|
+
el("button", { text: t("refresh"), onclick: () => void refresh(state, actions) })
|
|
1819
|
+
);
|
|
1820
|
+
void refresh(state, actions);
|
|
1821
|
+
head.appendChild(el("button", { text: t("close"), onclick: () => close(state) }));
|
|
1822
|
+
state.card.appendChild(head);
|
|
1823
|
+
state.card.setAttribute("role", "dialog");
|
|
1824
|
+
state.card.setAttribute("aria-label", t("panelTitle"));
|
|
1825
|
+
const tabs = el("div", { class: "dm-tabs" });
|
|
1826
|
+
tabs.appendChild(
|
|
1827
|
+
el("button", {
|
|
1828
|
+
class: "dm-tab",
|
|
1829
|
+
dataset: { tab: "servers" },
|
|
1830
|
+
text: t("tabServers"),
|
|
1831
|
+
onclick: () => switchTab(state, actions, "servers")
|
|
1832
|
+
})
|
|
1833
|
+
);
|
|
1834
|
+
tabs.appendChild(
|
|
1835
|
+
el("button", {
|
|
1836
|
+
class: "dm-tab",
|
|
1837
|
+
dataset: { tab: "quick" },
|
|
1838
|
+
text: t("tabQuickAdd"),
|
|
1839
|
+
onclick: () => switchTab(state, actions, "quick")
|
|
1840
|
+
})
|
|
1841
|
+
);
|
|
1842
|
+
state.card.appendChild(tabs);
|
|
1843
|
+
state.bodyEl = el("div", { class: "dm-body" });
|
|
1844
|
+
state.card.appendChild(state.bodyEl);
|
|
1845
|
+
state.overlay.appendChild(state.card);
|
|
1846
|
+
document.body.appendChild(state.overlay);
|
|
1847
|
+
const onKeyDown = (event) => {
|
|
1848
|
+
if (event.defaultPrevented) return;
|
|
1849
|
+
if (event.key === "Escape" && state.open) close(state);
|
|
1850
|
+
};
|
|
1851
|
+
document.addEventListener("keydown", onKeyDown);
|
|
1852
|
+
keydownCleanups.set(state, () => document.removeEventListener("keydown", onKeyDown));
|
|
1853
|
+
}
|
|
1854
|
+
if (!state.open) {
|
|
1855
|
+
state.panelOpener = typeof document !== "undefined" && document.activeElement instanceof HTMLElement ? document.activeElement : void 0;
|
|
1856
|
+
}
|
|
1857
|
+
state.open = true;
|
|
1858
|
+
if (state.card !== void 0) state.card.setAttribute("aria-modal", "true");
|
|
1859
|
+
state.overlay.hidden = false;
|
|
1860
|
+
state.overlay.classList.remove("dm-overlay--closing");
|
|
1861
|
+
const armOpen = () => {
|
|
1862
|
+
if (!state.open || state.overlay === void 0) return;
|
|
1863
|
+
state.overlay.style.animation = "none";
|
|
1864
|
+
void state.overlay.offsetWidth;
|
|
1865
|
+
state.overlay.style.animation = "";
|
|
1866
|
+
state.overlay.classList.add("dm-overlay--open");
|
|
1867
|
+
};
|
|
1868
|
+
if (typeof requestAnimationFrame === "function") requestAnimationFrame(armOpen);
|
|
1869
|
+
else armOpen();
|
|
1870
|
+
void refresh(state, actions);
|
|
1871
|
+
switchTab(state, actions, "servers");
|
|
1872
|
+
}
|
|
1873
|
+
function disposePanel(state) {
|
|
1874
|
+
const cleanup = keydownCleanups.get(state);
|
|
1875
|
+
if (cleanup !== void 0) {
|
|
1876
|
+
cleanup();
|
|
1877
|
+
keydownCleanups.delete(state);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
// src/client/core/session.ts
|
|
1882
|
+
function rebindSession(state) {
|
|
1883
|
+
if (!state.sessionResolved) return Promise.resolve(void 0);
|
|
1884
|
+
return api(state.API.session, {
|
|
1885
|
+
method: "POST",
|
|
1886
|
+
headers: { "content-type": "application/json" },
|
|
1887
|
+
body: JSON.stringify({ cwd: typeof state.currentCwd === "string" ? state.currentCwd : "" })
|
|
1888
|
+
}).catch(() => {
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
function warnUnknownOnce(state) {
|
|
1892
|
+
if (state.warnedUnknownSession) return;
|
|
1893
|
+
state.warnedUnknownSession = true;
|
|
1894
|
+
console.warn(
|
|
1895
|
+
"[dsh-mcp-manager] 读不到当前会话(官方 sessions.list 快照无 mainView 行):项目级 MCP 暂不绑定。未知不等于无项目,故不向宿主发送清空。"
|
|
1896
|
+
);
|
|
1897
|
+
}
|
|
1898
|
+
function bindSession(ctx, state, actions) {
|
|
1899
|
+
const list = ctx.sessions?.list;
|
|
1900
|
+
if (list === void 0) return () => {
|
|
1901
|
+
};
|
|
1902
|
+
const sync = () => {
|
|
1903
|
+
const read = readCurrentSession(list);
|
|
1904
|
+
const prevCwd = state.currentCwd;
|
|
1905
|
+
const prevResolved = state.sessionResolved;
|
|
1906
|
+
if (read.kind === "unknown") {
|
|
1907
|
+
state.currentCwd = void 0;
|
|
1908
|
+
state.sessionResolved = false;
|
|
1909
|
+
state.unknownSessionFrames += 1;
|
|
1910
|
+
state.updateFloatState?.();
|
|
1911
|
+
if (prevResolved || state.unknownSessionFrames >= 2) warnUnknownOnce(state);
|
|
1912
|
+
return;
|
|
1913
|
+
}
|
|
1914
|
+
state.currentCwd = read.cwd;
|
|
1915
|
+
state.sessionResolved = true;
|
|
1916
|
+
state.unknownSessionFrames = 0;
|
|
1917
|
+
state.warnedUnknownSession = false;
|
|
1918
|
+
state.updateFloatState?.();
|
|
1919
|
+
if (prevResolved && read.cwd === prevCwd) return;
|
|
1920
|
+
void api(state.API.session, {
|
|
1921
|
+
method: "POST",
|
|
1922
|
+
headers: { "content-type": "application/json" },
|
|
1923
|
+
body: JSON.stringify({ cwd: typeof read.cwd === "string" ? read.cwd : "" })
|
|
1924
|
+
}).then(() => actions.refresh()).catch(() => {
|
|
1925
|
+
});
|
|
1926
|
+
};
|
|
1927
|
+
sync();
|
|
1928
|
+
if (typeof list.subscribe === "function") return list.subscribe(sync);
|
|
1929
|
+
return () => {
|
|
1930
|
+
};
|
|
1931
|
+
}
|
|
1932
|
+
|
|
1933
|
+
// src/client/settings/settings-card.tsx
|
|
1934
|
+
var React2 = __toESM(require("react"), 1);
|
|
1935
|
+
|
|
1936
|
+
// src/vendor/shared/client/ui/index.tsx
|
|
1937
|
+
var React = require("react");
|
|
1938
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
1939
|
+
function Button(props) {
|
|
1940
|
+
const size = props.size ?? "md";
|
|
1941
|
+
const { size: _size, className, type, ...rest } = props;
|
|
1942
|
+
const cls = `dsu-btn dsu-btn-${size}${className ? ` ${className}` : ""}`;
|
|
1943
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: type ?? "button", className: cls, "data-dsu-btn": size, ...rest });
|
|
1944
|
+
}
|
|
1945
|
+
|
|
1946
|
+
// src/client/settings/settings-card.tsx
|
|
1947
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1948
|
+
function SettingsCard(props) {
|
|
1949
|
+
if (props.view === "summary") return null;
|
|
1950
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(SettingsPage, {});
|
|
1951
|
+
}
|
|
1952
|
+
function SettingsPage() {
|
|
1953
|
+
const useState2 = React2.useState;
|
|
1954
|
+
const useEffect2 = React2.useEffect;
|
|
1955
|
+
const [cfg, setCfg] = useState2(null);
|
|
1956
|
+
const [saving, setSaving] = useState2(false);
|
|
1957
|
+
const [msg, setMsg] = useState2(null);
|
|
1958
|
+
const msgTimer = React2.useRef(void 0);
|
|
1959
|
+
useEffect2(() => {
|
|
1960
|
+
let live = true;
|
|
1961
|
+
api(API.config).then((c) => {
|
|
1962
|
+
if (live && c !== null && typeof c === "object") {
|
|
1963
|
+
setCfg(c);
|
|
1964
|
+
}
|
|
1965
|
+
}).catch(() => {
|
|
1966
|
+
});
|
|
1967
|
+
return () => {
|
|
1968
|
+
live = false;
|
|
1969
|
+
if (msgTimer.current !== void 0) {
|
|
1970
|
+
clearTimeout(msgTimer.current);
|
|
1971
|
+
msgTimer.current = void 0;
|
|
1972
|
+
}
|
|
1973
|
+
};
|
|
1974
|
+
}, []);
|
|
1975
|
+
if (cfg === null) {
|
|
1976
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dm-set-card", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dm-set-body", children: t("settingsLoading") }) });
|
|
1977
|
+
}
|
|
1978
|
+
const set = (patch) => {
|
|
1979
|
+
setCfg((c) => c !== null ? Object.assign({}, c, patch) : c);
|
|
1980
|
+
};
|
|
1981
|
+
const numInput = (key, label, min = 0, max = 2e3) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("label", { className: "dm-set-field", children: [
|
|
1982
|
+
label,
|
|
1983
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
1984
|
+
"input",
|
|
1985
|
+
{
|
|
1986
|
+
className: "dm-set-input",
|
|
1987
|
+
type: "number",
|
|
1988
|
+
min,
|
|
1989
|
+
max,
|
|
1990
|
+
value: String(cfg[key]),
|
|
1991
|
+
onChange: (e) => {
|
|
1992
|
+
const v = Number(e.target.value);
|
|
1993
|
+
set({
|
|
1994
|
+
[key]: Number.isFinite(v) ? Math.min(max, Math.max(min, Math.round(v))) : min
|
|
1995
|
+
});
|
|
1996
|
+
}
|
|
1997
|
+
}
|
|
1998
|
+
)
|
|
1999
|
+
] });
|
|
2000
|
+
const save = async () => {
|
|
2001
|
+
setSaving(true);
|
|
2002
|
+
setMsg(null);
|
|
2003
|
+
try {
|
|
2004
|
+
const payload = { ...cfg };
|
|
2005
|
+
await api(API.config, {
|
|
2006
|
+
method: "POST",
|
|
2007
|
+
headers: { "content-type": "application/json" },
|
|
2008
|
+
body: JSON.stringify(payload)
|
|
2009
|
+
});
|
|
2010
|
+
setMsg({ ok: true, text: t("settingsSavedOk") });
|
|
2011
|
+
if (msgTimer.current !== void 0) clearTimeout(msgTimer.current);
|
|
2012
|
+
msgTimer.current = setTimeout(() => {
|
|
2013
|
+
msgTimer.current = void 0;
|
|
2014
|
+
setMsg(null);
|
|
2015
|
+
}, 2400);
|
|
2016
|
+
} catch (e) {
|
|
2017
|
+
setMsg({
|
|
2018
|
+
ok: false,
|
|
2019
|
+
text: t("saveFail", { msg: e instanceof Error ? e.message : String(e) })
|
|
2020
|
+
});
|
|
2021
|
+
}
|
|
2022
|
+
setSaving(false);
|
|
2023
|
+
};
|
|
2024
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dm-set-card", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dm-set-body", children: [
|
|
2025
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dm-set-row", children: [
|
|
2026
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("label", { htmlFor: "dm-set-position", children: t("anchorLabel") }),
|
|
2027
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
2028
|
+
"select",
|
|
2029
|
+
{
|
|
2030
|
+
id: "dm-set-position",
|
|
2031
|
+
className: "dm-set-input",
|
|
2032
|
+
value: cfg.position,
|
|
2033
|
+
onChange: (e) => {
|
|
2034
|
+
set({ position: e.target.value });
|
|
2035
|
+
},
|
|
2036
|
+
children: [
|
|
2037
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "top-right", children: t("posTopRight") }),
|
|
2038
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "top-left", children: t("posTopLeft") }),
|
|
2039
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "bottom-right", children: t("posBottomRight") }),
|
|
2040
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("option", { value: "bottom-left", children: t("posBottomLeft") })
|
|
2041
|
+
]
|
|
2042
|
+
}
|
|
2043
|
+
)
|
|
2044
|
+
] }),
|
|
2045
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dm-set-row", children: [
|
|
2046
|
+
numInput("offsetX", t("offsetX")),
|
|
2047
|
+
numInput("offsetY", t("offsetY")),
|
|
2048
|
+
numInput("blankY", t("blankY")),
|
|
2049
|
+
numInput("zIndexBase", t("zIndexBase"), 1, 9e3)
|
|
2050
|
+
] }),
|
|
2051
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "dm-set-hint", children: t("settingsHint") }),
|
|
2052
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "dm-set-foot", children: [
|
|
2053
|
+
msg !== null ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: msg.ok ? "dm-set-saved" : "dm-set-error", children: msg.text }) : null,
|
|
2054
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
2055
|
+
Button,
|
|
2056
|
+
{
|
|
2057
|
+
className: "dm-set-save",
|
|
2058
|
+
disabled: saving,
|
|
2059
|
+
onClick: () => {
|
|
2060
|
+
void save();
|
|
2061
|
+
},
|
|
2062
|
+
children: saving ? t("savingNow") : t("save")
|
|
2063
|
+
}
|
|
2064
|
+
)
|
|
2065
|
+
] })
|
|
2066
|
+
] }) });
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
// src/client/locales.ts
|
|
2070
|
+
var zh = {
|
|
2071
|
+
// 状态(constants.ts 状态表共用)
|
|
2072
|
+
stConnected: "运行中",
|
|
2073
|
+
stConnecting: "连接中",
|
|
2074
|
+
stReconnecting: "重连中",
|
|
2075
|
+
stStopped: "未连接",
|
|
2076
|
+
stDisabled: "已停用",
|
|
2077
|
+
stFailed: "失败",
|
|
2078
|
+
// 通用操作
|
|
2079
|
+
connect: "连接",
|
|
2080
|
+
disconnect: "断开",
|
|
2081
|
+
reconnect: "重连",
|
|
2082
|
+
enable: "启用",
|
|
2083
|
+
enableAndConnect: "启用并连接",
|
|
2084
|
+
disable: "禁用",
|
|
2085
|
+
edit: "编辑",
|
|
2086
|
+
delete: "删除",
|
|
2087
|
+
save: "保存",
|
|
2088
|
+
cancel: "取消",
|
|
2089
|
+
refresh: "刷新",
|
|
2090
|
+
close: "关闭",
|
|
2091
|
+
cancelEdit: "取消编辑",
|
|
2092
|
+
saveFail: "保存失败:{msg}",
|
|
2093
|
+
actionFail: "操作失败:{msg}",
|
|
2094
|
+
loadFail: "加载失败:{msg}",
|
|
2095
|
+
// 浮窗
|
|
2096
|
+
floatAriaLabel: "MCP 管理器",
|
|
2097
|
+
floatTitle: "MCP 管理器(点击展开)",
|
|
2098
|
+
floatManage: "管理",
|
|
2099
|
+
floatGlobalSession: "全局会话",
|
|
2100
|
+
groupProject: "项目级",
|
|
2101
|
+
groupGlobal: "全局",
|
|
2102
|
+
badgeScopeProject: "项目",
|
|
2103
|
+
badgeScopeGlobal: "全局",
|
|
2104
|
+
serverMeta: "{status} · {tools} 工具",
|
|
2105
|
+
toolsCount: "工具({n})",
|
|
2106
|
+
toolsCountPlain: "{n} 工具",
|
|
2107
|
+
statusGroupCount: "{status}({n})",
|
|
2108
|
+
groupAttention: "需关注({n})",
|
|
2109
|
+
healthRunning: "运行中 {n}",
|
|
2110
|
+
healthConnecting: "连接中 {n}",
|
|
2111
|
+
healthStopped: "未连接 {n}",
|
|
2112
|
+
healthFailed: "失败 {n}",
|
|
2113
|
+
floatEmptyTitle: "还没有 MCP 服务器",
|
|
2114
|
+
floatEmptyCta: "去添加",
|
|
2115
|
+
// 模态面板
|
|
2116
|
+
panelTitle: "MCP 管理器",
|
|
2117
|
+
tabServers: "服务器",
|
|
2118
|
+
tabQuickAdd: "快速接入",
|
|
2119
|
+
countsConnected: "运行中 {n}",
|
|
2120
|
+
countsConnecting: "连接中 {n}",
|
|
2121
|
+
countsFailed: "失败 {n}",
|
|
2122
|
+
countsSummary: "共 {n} 台 · {parts}",
|
|
2123
|
+
countsSummaryOnly: "共 {n} 台",
|
|
2124
|
+
// 快速接入表单
|
|
2125
|
+
addServer: "添加服务器",
|
|
2126
|
+
editServer: "编辑服务器:{name}",
|
|
2127
|
+
nameLabel: "服务器名称(唯一,作为 mcp__<name>__ 前缀)",
|
|
2128
|
+
ownershipLabel: "归属",
|
|
2129
|
+
transportLabel: "传输类型",
|
|
2130
|
+
commandLabel: "命令(stdio)",
|
|
2131
|
+
argsLabel: "参数(逗号分隔)",
|
|
2132
|
+
envLabel: "环境变量(每行 KEY=VALUE,支持 ${ENV} 引用,值为空则继承父环境)",
|
|
2133
|
+
cwdLabel: "工作目录(可选)",
|
|
2134
|
+
urlLabel: "URL(streamable-http)",
|
|
2135
|
+
headersLabel: "请求头(每行 KEY: VALUE,支持 ${ENV} 引用)",
|
|
2136
|
+
enabledLabel: "启用(保存后立即连接)",
|
|
2137
|
+
scopeProjectOpt: "项目级(<项目>/.dsh/dsh-mcp-manager/mcp.json,随会话切换)",
|
|
2138
|
+
scopeGlobalOpt: "全局(本机配置)",
|
|
2139
|
+
transportStdioOpt: "stdio(本地子进程)",
|
|
2140
|
+
transportHttpOpt: "streamable-http(远程)",
|
|
2141
|
+
envPlaceholder: "每行 KEY=VALUE,如\nCONTEXT7_API_KEY=${CONTEXT7_API_KEY}",
|
|
2142
|
+
headersPlaceholder: "每行 KEY: VALUE,支持 ${ENV} 引用,如\nAuthorization: Bearer ${CONTEXT7_API_KEY}",
|
|
2143
|
+
cwdPlaceholder: "可选工作目录",
|
|
2144
|
+
sourcesTitle: "mcp.json 配置文件(保存即互斥同步)",
|
|
2145
|
+
srcGlobalAgents: "全局 .agents/mcp.json",
|
|
2146
|
+
srcGlobalDsh: "全局 .dsh/mcp.json",
|
|
2147
|
+
srcProjectAgents: "项目 .agents/mcp.json",
|
|
2148
|
+
srcProjectDsh: "项目 .dsh/mcp.json",
|
|
2149
|
+
srcNotExists: "尚不存在,保存时创建",
|
|
2150
|
+
srcNeedSession: "需要项目会话后可用",
|
|
2151
|
+
srcUngrouped: "其他(未声明来源)",
|
|
2152
|
+
srcQuickSetup: "快速配置该来源",
|
|
2153
|
+
srcEdit: "编辑",
|
|
2154
|
+
srcSave: "保存并同步",
|
|
2155
|
+
srcSavedOk: "已保存并同步:{names}",
|
|
2156
|
+
srcMutexRemoved: "互斥移除同名:{names}",
|
|
2157
|
+
srcActivateThis: "启用此来源",
|
|
2158
|
+
jsonNameFixed: "JSON 里的 name 不可改(如需改名请删除后新建)",
|
|
2159
|
+
pasteTitle: "粘贴 mcpServers JSON 导入",
|
|
2160
|
+
importJson: "导入 JSON",
|
|
2161
|
+
importFromFile: "从 mcp.json 文件导入",
|
|
2162
|
+
importPathPlaceholder: "或填 mcp.json 文件路径导入,如 ~/.claude/mcp.json",
|
|
2163
|
+
importedOk: "已导入:{names}",
|
|
2164
|
+
importedNone: "(无)",
|
|
2165
|
+
importSkipped: "跳过(已存在):{names}",
|
|
2166
|
+
importFail: "导入失败:{msg}",
|
|
2167
|
+
// 服务器列表页
|
|
2168
|
+
serversEmpty: "还没有配置 MCP 服务器。切到「快速接入」页添加,或粘贴 mcpServers JSON 导入。",
|
|
2169
|
+
confirmDelete: "删除 MCP 服务器「{name}」?",
|
|
2170
|
+
// 设置卡
|
|
2171
|
+
settingsLoading: "MCP 管理器:加载中…",
|
|
2172
|
+
settingsName: "MCP 管理器(dsh-mcp-manager)",
|
|
2173
|
+
settingsDescription: "浮窗位置 / 水平·垂直·空白偏移",
|
|
2174
|
+
anchorLabel: "锚点",
|
|
2175
|
+
posTopRight: "右上(top-right)",
|
|
2176
|
+
posTopLeft: "左上(top-left)",
|
|
2177
|
+
posBottomRight: "右下(bottom-right)",
|
|
2178
|
+
posBottomLeft: "左下(bottom-left)",
|
|
2179
|
+
offsetX: "水平偏移",
|
|
2180
|
+
offsetY: "垂直偏移",
|
|
2181
|
+
blankY: "空白偏移",
|
|
2182
|
+
zIndexBase: "层级基准",
|
|
2183
|
+
settingsHint: "保存即热更新:浮窗位置与偏移即时生效并持久化(无需重启 dsh web)。",
|
|
2184
|
+
savingNow: "保存中…",
|
|
2185
|
+
settingsSavedOk: "已保存——浮窗位置即时生效(无需重启)"
|
|
2186
|
+
};
|
|
2187
|
+
var en = {
|
|
2188
|
+
stConnected: "Running",
|
|
2189
|
+
stConnecting: "Connecting",
|
|
2190
|
+
stReconnecting: "Reconnecting",
|
|
2191
|
+
stStopped: "Disconnected",
|
|
2192
|
+
stDisabled: "Disabled",
|
|
2193
|
+
stFailed: "Failed",
|
|
2194
|
+
connect: "Connect",
|
|
2195
|
+
disconnect: "Disconnect",
|
|
2196
|
+
reconnect: "Reconnect",
|
|
2197
|
+
enable: "Enable",
|
|
2198
|
+
enableAndConnect: "Enable & connect",
|
|
2199
|
+
disable: "Disable",
|
|
2200
|
+
edit: "Edit",
|
|
2201
|
+
delete: "Delete",
|
|
2202
|
+
save: "Save",
|
|
2203
|
+
cancel: "Cancel",
|
|
2204
|
+
refresh: "Refresh",
|
|
2205
|
+
close: "Close",
|
|
2206
|
+
cancelEdit: "Cancel edit",
|
|
2207
|
+
saveFail: "Save failed: {msg}",
|
|
2208
|
+
actionFail: "Operation failed: {msg}",
|
|
2209
|
+
loadFail: "Load failed: {msg}",
|
|
2210
|
+
floatAriaLabel: "MCP manager",
|
|
2211
|
+
floatTitle: "MCP manager (click to expand)",
|
|
2212
|
+
floatManage: "Manage",
|
|
2213
|
+
floatGlobalSession: "global session",
|
|
2214
|
+
groupProject: "Project",
|
|
2215
|
+
groupGlobal: "Global",
|
|
2216
|
+
badgeScopeProject: "Project",
|
|
2217
|
+
badgeScopeGlobal: "Global",
|
|
2218
|
+
serverMeta: "{status} · {tools} tools",
|
|
2219
|
+
toolsCount: "Tools ({n})",
|
|
2220
|
+
toolsCountPlain: "{n} tools",
|
|
2221
|
+
statusGroupCount: "{status} ({n})",
|
|
2222
|
+
groupAttention: "Needs attention ({n})",
|
|
2223
|
+
healthRunning: "{n} running",
|
|
2224
|
+
healthConnecting: "{n} connecting",
|
|
2225
|
+
healthStopped: "{n} disconnected",
|
|
2226
|
+
healthFailed: "{n} failed",
|
|
2227
|
+
floatEmptyTitle: "No MCP servers yet",
|
|
2228
|
+
floatEmptyCta: "Add one",
|
|
2229
|
+
panelTitle: "MCP manager",
|
|
2230
|
+
tabServers: "Servers",
|
|
2231
|
+
tabQuickAdd: "Quick add",
|
|
2232
|
+
countsConnected: "running {n}",
|
|
2233
|
+
countsConnecting: "connecting {n}",
|
|
2234
|
+
countsFailed: "failed {n}",
|
|
2235
|
+
countsSummary: "{n} total · {parts}",
|
|
2236
|
+
countsSummaryOnly: "{n} total",
|
|
2237
|
+
addServer: "Add server",
|
|
2238
|
+
editServer: "Edit server: {name}",
|
|
2239
|
+
nameLabel: "Server name (unique, used as the mcp__<name>__ prefix)",
|
|
2240
|
+
ownershipLabel: "Scope",
|
|
2241
|
+
transportLabel: "Transport",
|
|
2242
|
+
commandLabel: "Command (stdio)",
|
|
2243
|
+
argsLabel: "Args (comma-separated)",
|
|
2244
|
+
envLabel: "Env vars (KEY=VALUE per line, ${ENV} refs supported; empty value inherits the parent environment)",
|
|
2245
|
+
cwdLabel: "Working directory (optional)",
|
|
2246
|
+
urlLabel: "URL (streamable-http)",
|
|
2247
|
+
headersLabel: "Headers (KEY: VALUE per line, ${ENV} refs supported)",
|
|
2248
|
+
enabledLabel: "Enabled (connects immediately after saving)",
|
|
2249
|
+
scopeProjectOpt: "Project (<project>/.dsh/dsh-mcp-manager/mcp.json, follows the session)",
|
|
2250
|
+
scopeGlobalOpt: "Global (local machine config)",
|
|
2251
|
+
transportStdioOpt: "stdio (local subprocess)",
|
|
2252
|
+
transportHttpOpt: "streamable-http (remote)",
|
|
2253
|
+
envPlaceholder: "One KEY=VALUE per line, e.g.\nCONTEXT7_API_KEY=${CONTEXT7_API_KEY}",
|
|
2254
|
+
headersPlaceholder: "One KEY: VALUE per line, ${ENV} refs supported, e.g.\nAuthorization: Bearer ${CONTEXT7_API_KEY}",
|
|
2255
|
+
cwdPlaceholder: "Optional working directory",
|
|
2256
|
+
sourcesTitle: "mcp.json config files (save = mutex sync)",
|
|
2257
|
+
srcGlobalAgents: "Global .agents/mcp.json",
|
|
2258
|
+
srcGlobalDsh: "Global .dsh/mcp.json",
|
|
2259
|
+
srcProjectAgents: "Project .agents/mcp.json",
|
|
2260
|
+
srcProjectDsh: "Project .dsh/mcp.json",
|
|
2261
|
+
srcNotExists: "not created yet (created on save)",
|
|
2262
|
+
srcNeedSession: "available after a project session starts",
|
|
2263
|
+
srcUngrouped: "Other (no declared source)",
|
|
2264
|
+
srcQuickSetup: "Quick-configure this source",
|
|
2265
|
+
srcEdit: "Edit",
|
|
2266
|
+
srcSave: "Save & sync",
|
|
2267
|
+
srcSavedOk: "Saved & synced: {names}",
|
|
2268
|
+
srcMutexRemoved: "Mutex-removed same-name: {names}",
|
|
2269
|
+
srcActivateThis: "Activate this source",
|
|
2270
|
+
jsonNameFixed: "the name field is fixed (delete & recreate to rename)",
|
|
2271
|
+
pasteTitle: "Paste mcpServers JSON to import",
|
|
2272
|
+
importJson: "Import JSON",
|
|
2273
|
+
importFromFile: "Import from file",
|
|
2274
|
+
importPathPlaceholder: "Or import from an mcp.json file path, e.g. ~/.claude/mcp.json",
|
|
2275
|
+
importedOk: "Imported: {names}",
|
|
2276
|
+
importedNone: "(none)",
|
|
2277
|
+
importSkipped: "Skipped (already present): {names}",
|
|
2278
|
+
importFail: "Import failed: {msg}",
|
|
2279
|
+
serversEmpty: 'No MCP servers yet. Add one on the "Quick add" tab, or paste mcpServers JSON to import.',
|
|
2280
|
+
confirmDelete: 'Delete MCP server "{name}"?',
|
|
2281
|
+
settingsLoading: "MCP manager: loading…",
|
|
2282
|
+
settingsName: "MCP manager (dsh-mcp-manager)",
|
|
2283
|
+
settingsDescription: "Float placement / horizontal·vertical·gap offsets",
|
|
2284
|
+
anchorLabel: "Anchor",
|
|
2285
|
+
posTopRight: "Top right (top-right)",
|
|
2286
|
+
posTopLeft: "Top left (top-left)",
|
|
2287
|
+
posBottomRight: "Bottom right (bottom-right)",
|
|
2288
|
+
posBottomLeft: "Bottom left (bottom-left)",
|
|
2289
|
+
offsetX: "Horizontal offset",
|
|
2290
|
+
offsetY: "Vertical offset",
|
|
2291
|
+
blankY: "Blank-session offset",
|
|
2292
|
+
zIndexBase: "Z-index base",
|
|
2293
|
+
settingsHint: "Hot reload on save: float placement and offsets apply immediately and persist (no dsh web restart).",
|
|
2294
|
+
savingNow: "Saving…",
|
|
2295
|
+
settingsSavedOk: "Saved — float placement takes effect immediately (no restart)"
|
|
2296
|
+
};
|
|
2297
|
+
|
|
2298
|
+
// src/client/index.ts
|
|
2299
|
+
var NS = "mcpManager";
|
|
2300
|
+
var ROW_CONFIG_SLOT = "plugins.row.config";
|
|
2301
|
+
function apply(ctx) {
|
|
2302
|
+
const state = createState();
|
|
2303
|
+
const actions = {
|
|
2304
|
+
refresh: () => refresh(state, actions),
|
|
2305
|
+
switchTab: (tab) => switchTab(state, actions, tab),
|
|
2306
|
+
close: () => close(state),
|
|
2307
|
+
showPanel: () => showPanel(state, actions),
|
|
2308
|
+
toggleFloat: (force) => toggleFloat(state, actions, force),
|
|
2309
|
+
resetForm: () => resetForm(state),
|
|
2310
|
+
beginEdit: (server) => beginEdit(state, actions, server)
|
|
2311
|
+
};
|
|
2312
|
+
try {
|
|
2313
|
+
setFallbackDict(zh);
|
|
2314
|
+
const locale = ctx.get("locale");
|
|
2315
|
+
let unsubLocale;
|
|
2316
|
+
if (locale && typeof locale.register === "function") {
|
|
2317
|
+
try {
|
|
2318
|
+
locale.register(NS, { zh, en });
|
|
2319
|
+
bindLocale(locale, NS);
|
|
2320
|
+
if (typeof locale.subscribe === "function" && typeof locale.getSnapshot === "function") {
|
|
2321
|
+
unsubLocale = locale.subscribe(function() {
|
|
2322
|
+
bindLocale(locale, NS);
|
|
2323
|
+
});
|
|
2324
|
+
}
|
|
2325
|
+
} catch (error) {
|
|
2326
|
+
console.warn("[dsh-mcp-manager] locale 注册失败:", error);
|
|
2327
|
+
}
|
|
2328
|
+
}
|
|
2329
|
+
ensureStyle({ id: "dsh-mcp-manager-style", cssText: ui_default + style_default });
|
|
2330
|
+
const disposers = [];
|
|
2331
|
+
const slots = ctx.get("slots");
|
|
2332
|
+
const configForms = ctx.get("configForms");
|
|
2333
|
+
if (slots !== void 0 && configForms !== void 0) {
|
|
2334
|
+
ctx.effect(
|
|
2335
|
+
() => configForms.whileServed(
|
|
2336
|
+
[MCP_MANAGER_IDENTITY.settingsNamespace],
|
|
2337
|
+
() => slots.inject(
|
|
2338
|
+
ROW_CONFIG_SLOT,
|
|
2339
|
+
() => slots.register(
|
|
2340
|
+
{
|
|
2341
|
+
name: ROW_CONFIG_SLOT,
|
|
2342
|
+
key: MCP_MANAGER_IDENTITY.rowConfigKey,
|
|
2343
|
+
locale: NS
|
|
2344
|
+
},
|
|
2345
|
+
(props) => React3.createElement(SettingsCard, props)
|
|
2346
|
+
)
|
|
2347
|
+
)
|
|
2348
|
+
),
|
|
2349
|
+
"dsh-mcp-manager: row config page"
|
|
2350
|
+
);
|
|
2351
|
+
}
|
|
2352
|
+
disposers.push(mountFloat(ctx, state, actions));
|
|
2353
|
+
disposers.push(bindSession(ctx, state, actions));
|
|
2354
|
+
api(state.API.config).then((cfg) => {
|
|
2355
|
+
if (cfg !== null && typeof cfg === "object") state.mcpUiConfig = cfg;
|
|
2356
|
+
state.updateFloatState?.();
|
|
2357
|
+
}).catch(() => {
|
|
2358
|
+
});
|
|
2359
|
+
let retryTimer = void 0;
|
|
2360
|
+
const attemptRefresh = (attempt) => {
|
|
2361
|
+
refresh(state, actions).then((ok) => {
|
|
2362
|
+
if (!ok && attempt < 3) {
|
|
2363
|
+
retryTimer = setTimeout(() => attemptRefresh(attempt + 1), 500 * 2 ** attempt);
|
|
2364
|
+
}
|
|
2365
|
+
}).catch((error) => {
|
|
2366
|
+
console.warn("[dsh-mcp-manager] 首刷失败:", error);
|
|
2367
|
+
});
|
|
2368
|
+
};
|
|
2369
|
+
attemptRefresh(0);
|
|
2370
|
+
let es = void 0;
|
|
2371
|
+
let refreshTimer = void 0;
|
|
2372
|
+
let esFailures = 0;
|
|
2373
|
+
let pollTimer = void 0;
|
|
2374
|
+
const WATCHDOG_MS = 6e4;
|
|
2375
|
+
let lastActivity = 0;
|
|
2376
|
+
let lastReconnectAt = 0;
|
|
2377
|
+
let watchdog = void 0;
|
|
2378
|
+
let eventsRetired = false;
|
|
2379
|
+
const scheduleRefresh = () => {
|
|
2380
|
+
if (refreshTimer !== void 0) return;
|
|
2381
|
+
refreshTimer = setTimeout(() => {
|
|
2382
|
+
refreshTimer = void 0;
|
|
2383
|
+
void refresh(state, actions).catch(() => {
|
|
2384
|
+
});
|
|
2385
|
+
}, 500);
|
|
2386
|
+
};
|
|
2387
|
+
const startPolling = () => {
|
|
2388
|
+
eventsRetired = true;
|
|
2389
|
+
if (watchdog !== void 0) {
|
|
2390
|
+
clearTimeout(watchdog);
|
|
2391
|
+
watchdog = void 0;
|
|
2392
|
+
}
|
|
2393
|
+
if (pollTimer !== void 0) return;
|
|
2394
|
+
let pollTicks = 0;
|
|
2395
|
+
const tick = () => {
|
|
2396
|
+
pollTimer = setTimeout(() => {
|
|
2397
|
+
void refresh(state, actions).catch(() => {
|
|
2398
|
+
});
|
|
2399
|
+
pollTicks += 1;
|
|
2400
|
+
if (pollTicks % 5 === 0) tryResumeEvents();
|
|
2401
|
+
tick();
|
|
2402
|
+
}, 1e4);
|
|
2403
|
+
};
|
|
2404
|
+
tick();
|
|
2405
|
+
};
|
|
2406
|
+
const tryResumeEvents = () => {
|
|
2407
|
+
if (pollTimer !== void 0) {
|
|
2408
|
+
clearTimeout(pollTimer);
|
|
2409
|
+
pollTimer = void 0;
|
|
2410
|
+
}
|
|
2411
|
+
eventsRetired = false;
|
|
2412
|
+
esFailures = 0;
|
|
2413
|
+
connectEvents();
|
|
2414
|
+
};
|
|
2415
|
+
const closeEvents = () => {
|
|
2416
|
+
if (es === void 0) return;
|
|
2417
|
+
try {
|
|
2418
|
+
es.close();
|
|
2419
|
+
} catch (error) {
|
|
2420
|
+
console.warn("[dsh-mcp-manager] 关闭旧 SSE 连接失败:", error);
|
|
2421
|
+
}
|
|
2422
|
+
es = void 0;
|
|
2423
|
+
};
|
|
2424
|
+
const armWatchdog = () => {
|
|
2425
|
+
if (watchdog !== void 0) clearTimeout(watchdog);
|
|
2426
|
+
watchdog = setTimeout(() => {
|
|
2427
|
+
if (Date.now() - lastActivity > WATCHDOG_MS) {
|
|
2428
|
+
forceReconnect();
|
|
2429
|
+
} else {
|
|
2430
|
+
armWatchdog();
|
|
2431
|
+
}
|
|
2432
|
+
}, WATCHDOG_MS + 5e3);
|
|
2433
|
+
};
|
|
2434
|
+
let lastRecoverAt = 0;
|
|
2435
|
+
let lastResumeAt = 0;
|
|
2436
|
+
let lastResumeCwd = void 0;
|
|
2437
|
+
let resumeSeq = 0;
|
|
2438
|
+
let lastResumeSeq = 0;
|
|
2439
|
+
const postResume = () => {
|
|
2440
|
+
const body = resumeBodyOf(state);
|
|
2441
|
+
const key = body?.cwd ?? "<unknown>";
|
|
2442
|
+
const now = Date.now();
|
|
2443
|
+
if (key === lastResumeCwd && now - lastResumeAt < 5e3) return;
|
|
2444
|
+
resumeSeq += 1;
|
|
2445
|
+
const attempt = resumeSeq;
|
|
2446
|
+
lastResumeAt = now;
|
|
2447
|
+
lastResumeCwd = key;
|
|
2448
|
+
lastResumeSeq = attempt;
|
|
2449
|
+
const init = body === void 0 ? { method: "POST" } : {
|
|
2450
|
+
method: "POST",
|
|
2451
|
+
headers: { "content-type": "application/json" },
|
|
2452
|
+
body: JSON.stringify(body)
|
|
2453
|
+
};
|
|
2454
|
+
void api(state.API.resume, init).catch(() => {
|
|
2455
|
+
if (lastResumeSeq === attempt) {
|
|
2456
|
+
lastResumeAt = 0;
|
|
2457
|
+
lastResumeCwd = void 0;
|
|
2458
|
+
}
|
|
2459
|
+
});
|
|
2460
|
+
};
|
|
2461
|
+
const maybeRecoverSession = () => {
|
|
2462
|
+
const now = Date.now();
|
|
2463
|
+
if (now - lastRecoverAt < 1e4) return;
|
|
2464
|
+
void api(state.API.servers).then((payload) => {
|
|
2465
|
+
if (payload?.projectRoot !== void 0) return;
|
|
2466
|
+
const cwd = state.currentCwd;
|
|
2467
|
+
if (typeof cwd !== "string" || cwd === "") return;
|
|
2468
|
+
lastRecoverAt = Date.now();
|
|
2469
|
+
void rebindSession(state).then(() => postResume()).catch(() => {
|
|
2470
|
+
});
|
|
2471
|
+
}).catch(() => {
|
|
2472
|
+
});
|
|
2473
|
+
};
|
|
2474
|
+
const connectEvents = () => {
|
|
2475
|
+
closeEvents();
|
|
2476
|
+
try {
|
|
2477
|
+
es = new EventSource(state.API.events);
|
|
2478
|
+
lastActivity = Date.now();
|
|
2479
|
+
es.onopen = () => {
|
|
2480
|
+
maybeRecoverSession();
|
|
2481
|
+
};
|
|
2482
|
+
es.onmessage = (ev) => {
|
|
2483
|
+
lastActivity = Date.now();
|
|
2484
|
+
let msg;
|
|
2485
|
+
try {
|
|
2486
|
+
msg = JSON.parse(String(ev.data));
|
|
2487
|
+
} catch {
|
|
2488
|
+
msg = void 0;
|
|
2489
|
+
}
|
|
2490
|
+
if (msg?.type === SSE_FRAMES.ping) return;
|
|
2491
|
+
if (msg !== void 0 && msg.type === SSE_FRAMES.uiConfigChanged) {
|
|
2492
|
+
void api(state.API.config).then((cfg) => {
|
|
2493
|
+
if (cfg !== null && typeof cfg === "object") {
|
|
2494
|
+
state.mcpUiConfig = cfg;
|
|
2495
|
+
}
|
|
2496
|
+
state.updateFloatState?.();
|
|
2497
|
+
if (state.floatOpen) renderFloatPanel(state, actions);
|
|
2498
|
+
}).catch(() => {
|
|
2499
|
+
});
|
|
2500
|
+
} else {
|
|
2501
|
+
scheduleRefresh();
|
|
2502
|
+
}
|
|
2503
|
+
};
|
|
2504
|
+
es.onerror = () => {
|
|
2505
|
+
if (es !== void 0 && es.readyState === EventSource.CLOSED) {
|
|
2506
|
+
esFailures += 1;
|
|
2507
|
+
if (esFailures >= 3) {
|
|
2508
|
+
closeEvents();
|
|
2509
|
+
startPolling();
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
};
|
|
2513
|
+
armWatchdog();
|
|
2514
|
+
} catch {
|
|
2515
|
+
startPolling();
|
|
2516
|
+
}
|
|
2517
|
+
};
|
|
2518
|
+
const forceReconnect = () => {
|
|
2519
|
+
if (eventsRetired) return;
|
|
2520
|
+
const now = Date.now();
|
|
2521
|
+
if (now - lastReconnectAt < 5e3) return;
|
|
2522
|
+
lastReconnectAt = now;
|
|
2523
|
+
connectEvents();
|
|
2524
|
+
};
|
|
2525
|
+
connectEvents();
|
|
2526
|
+
const onVisible = () => {
|
|
2527
|
+
if (document.hidden) return;
|
|
2528
|
+
forceReconnect();
|
|
2529
|
+
void rebindSession(state).then(() => postResume()).catch(() => {
|
|
2530
|
+
});
|
|
2531
|
+
void refresh(state, actions).catch(() => {
|
|
2532
|
+
});
|
|
2533
|
+
};
|
|
2534
|
+
document.addEventListener("visibilitychange", onVisible);
|
|
2535
|
+
const onPageShow = (event) => {
|
|
2536
|
+
if (event?.persisted === true) onVisible();
|
|
2537
|
+
};
|
|
2538
|
+
window.addEventListener("pageshow", onPageShow);
|
|
2539
|
+
ctx.effect(
|
|
2540
|
+
() => () => {
|
|
2541
|
+
clearTimeout(retryTimer);
|
|
2542
|
+
if (refreshTimer !== void 0) clearTimeout(refreshTimer);
|
|
2543
|
+
if (pollTimer !== void 0) clearTimeout(pollTimer);
|
|
2544
|
+
if (watchdog !== void 0) clearTimeout(watchdog);
|
|
2545
|
+
closeEvents();
|
|
2546
|
+
document.removeEventListener("visibilitychange", onVisible);
|
|
2547
|
+
window.removeEventListener("pageshow", onPageShow);
|
|
2548
|
+
if (unsubLocale !== void 0) unsubLocale();
|
|
2549
|
+
for (const dispose of disposers.splice(0)) dispose();
|
|
2550
|
+
disposePanel(state);
|
|
2551
|
+
if (state.overlay !== void 0 && state.overlay.parentElement !== null)
|
|
2552
|
+
state.overlay.remove();
|
|
2553
|
+
const savedUiConfig = state.mcpUiConfig;
|
|
2554
|
+
Object.assign(state, createState());
|
|
2555
|
+
state.mcpUiConfig = savedUiConfig;
|
|
2556
|
+
},
|
|
2557
|
+
"dsh-mcp-manager: ui"
|
|
2558
|
+
);
|
|
2559
|
+
} catch (error) {
|
|
2560
|
+
console.warn("[dsh-mcp-manager] mount failed:", error);
|
|
2561
|
+
}
|
|
2562
|
+
}
|
|
2563
|
+
var inject = ["sessions", "slots", "configForms", "locale"];
|
|
2564
|
+
|
|
2565
|
+
Object.defineProperty(module.exports, Symbol.toStringTag, { value: 'Module' })
|
|
2566
|
+
return module.exports
|
|
2567
|
+
}
|
|
2568
|
+
})
|