@zaimokuza/dsh-plugin-hub 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # DSH Plugin Hub
2
2
 
3
- DSH 原生资源面板,当前版本 **0.2.1**。支持 Web 与 Desktop、中英文界面;从侧边栏打开,保留当前会话。
3
+ DSH 原生资源面板,当前版本 **0.2.3**。支持 Web 与 Desktop、中英文界面;从右上角 Plugin Hub 图标打开,点击关闭图标返回当前会话。
4
4
 
5
5
  - **Skill**:搜索、按工作区筛选、启停、打开目录,使用 DSH 原生 Markdown 组件预览正文。
6
6
  - **MCP**:JSON/YAML 配置编辑与格式化、环境变量引用、字段帮助、启停、重连、独立连接测试和删除。
@@ -15,13 +15,13 @@ dsh plugin --profile <当前-profile> add @zaimokuza/dsh-plugin-hub
15
15
 
16
16
  重启 DSH 后打开 Plugin Hub。Desktop 从自身的 **Desktop Plugins** 窗口安装。
17
17
 
18
- 要求 Node.js `^22.19.0 || >=24.0.0`;已验证 DSH `0.1.5-rc.1` `0.1.5-rc.2`。
18
+ 要求 Node.js `^22.19.0 || >=24.0.0`;兼容性验证覆盖 DSH `0.1.5-rc.1`、`0.1.5-rc.2` `0.1.6-alpha.1`。
19
19
 
20
20
  ## 使用说明
21
21
 
22
22
  所有管理操作绑定当前实例的 profile。Skill 启停不会修改共享文件;MCP 删除会移除 Hub 创建的配置或当前 profile 中独立的 YAML 定义,共享文件或安装包中的定义需在来源处删除。
23
23
 
24
- MCP 的描述、版本和图标来自服务初始化信息。宿主未公开持续连接状态时显示“未知”,独立连接测试单独显示结果。建议在启动 DSH 时设置凭据环境变量,在 YAML 中通过 `!!js process.env.MCP_TOKEN` 引用;JSON 等价写法为 `{ "__jsExpr": "process.env.MCP_TOKEN" }`。
24
+ MCP 配置校验和字段帮助直接读取当前 DSH 的原生规则与默认值。独立连接测试支持新旧 MCP 协议,描述、版本和图标来自服务元数据,服务端指令不作为描述。宿主未公开持续连接状态时显示“未知”,独立连接测试单独显示结果。建议在启动 DSH 时设置凭据环境变量,在 YAML 中通过 `!!js process.env.MCP_TOKEN` 引用;JSON 等价写法为 `{ "__jsExpr": "process.env.MCP_TOKEN" }`。
25
25
 
26
26
  Desktop 实验性功能通过宿主的 `window.dshDesktop.experiments` v1 接口执行,宿主完成空闲检查、配置事务和后端重启。缺少接口的 Desktop 显示不支持管理。
27
27
 
@@ -16,6 +16,6 @@ The above copyright notice and this permission notice shall be included in all c
16
16
 
17
17
  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
18
18
 
19
- Runtime dependencies @modelcontextprotocol/sdk and yaml, and build dependencies esbuild and React retain their own licenses. Their precise versions are recorded in package-lock.json.
19
+ Runtime dependencies @modelcontextprotocol/client and yaml, and development/test dependencies @modelcontextprotocol/sdk, @modelcontextprotocol/server, @modelcontextprotocol/node, @deepseek-ai/schemastery, esbuild and React retain their own licenses. Their precise versions are recorded in package-lock.json.
20
20
 
21
21
  The UI consumes the host-provided `@deepseek-ai/dsh-client-ui-primitives` (Input, Button, Menu and Modal). These components and their styles are resolved from the DSH module table, not copied or bundled into this plugin.
package/lib/client.js CHANGED
@@ -40,11 +40,11 @@ module.exports = __toCommonJS(index_exports);
40
40
  var define_HUB_BRAND_default = { title: "Plugin Hub", subTitle: "DSH EXTENSIONS", primaryColor: "#596579", navTitle: "Plugin Hub" };
41
41
 
42
42
  // src/client/index.jsx
43
- var import_react2 = __toESM(require("react"), 1);
43
+ var import_react5 = __toESM(require("react"), 1);
44
44
 
45
45
  // src/client/resources.jsx
46
- var import_react = __toESM(require("react"), 1);
47
- var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
46
+ var import_react4 = __toESM(require("react"), 1);
47
+ var import_dsh_client_ui_primitives4 = require("@deepseek-ai/dsh-client-ui-primitives");
48
48
 
49
49
  // src/client/resources.css
50
50
  var resources_default = `/* Reuse marketplace layout and DSH controls; only adapt the main-panel container. */
@@ -78,12 +78,6 @@ var resources_default = `/* Reuse marketplace layout and DSH controls; only adap
78
78
  .hub-resource-card-heading { display: flex; align-items: center; justify-content: space-between; gap: 12px; min-height: 28px; }
79
79
  .hub-resource-card-heading > :last-child { flex-shrink: 0; }
80
80
  .hub-resource-tab[aria-selected="true"], .hub-resource-tab[aria-pressed="true"] { background: var(--dsw-alias-interactive-bg-active); }
81
- /* The sidebar exports a slot, not its Settings trigger; match that trigger's geometry. */
82
- .hub-sidebar-button { box-sizing: border-box; flex: 1; min-width: 0; height: 42px; margin: 4px -2px; padding: 0 10px 0 8px; gap: 8px; justify-content: flex-start; border-radius: 12px; font-family: var(--dsw-font-family); }
83
- .hub-sidebar-button[aria-pressed="true"] { background: var(--dsw-alias-interactive-bg-active); }
84
- .hub-sidebar-button > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
85
- .hub-sidebar-button.is-rail { flex: none; width: 36px; height: 36px; margin: 8px 0 10px; padding: 0; justify-content: center; border-radius: 50%; }
86
-
87
81
  .hub-resource-page .hub-search-input input::placeholder { color: var(--dsw-alias-label-tertiary); opacity: 1; }
88
82
  /* Typography matches DSH's 14px controls; secondary metadata uses 12px. */
89
83
  .hub-resource-page .hub-market { font: inherit; }
@@ -119,53 +113,13 @@ var resources_default = `/* Reuse marketplace layout and DSH controls; only adap
119
113
  .hub-mcp-endpoint code { font: 11px/18px var(--ds-font-family-code); max-width: 180px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
120
114
  `;
121
115
 
122
- // src/client/mcp-help.js
123
- var mcpFields = [
124
- ["serverName", "\u5DE5\u5177\u547D\u540D\u7A7A\u95F4\uFF0C1\u201332 \u4F4D\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u6216\u8FDE\u5B57\u7B26\u3002", "\u5FC5\u586B"],
125
- ["transport", "stdio \u4E3A\u672C\u5730\u8FDB\u7A0B\uFF1Bstreamable-http \u4E3A HTTP \u670D\u52A1\u3002", "\u5FC5\u586B"],
126
- ["command", "stdio \u53EF\u6267\u884C\u6587\u4EF6\uFF1BWindows \u53EF\u4F7F\u7528 node.exe \u7684\u5B8C\u6574\u8DEF\u5F84\u3002", "stdio \u5FC5\u586B"],
127
- ["args", "\u4F20\u7ED9\u53EF\u6267\u884C\u6587\u4EF6\u7684\u5B57\u7B26\u4E32\u6570\u7EC4\uFF0C\u4E0D\u7ECF\u8FC7 shell\u3002", "[]"],
128
- ["env", "\u663E\u5F0F\u4F20\u7ED9\u670D\u52A1\u7684\u73AF\u5883\u53D8\u91CF\uFF0C\u53EF\u7528 !!js process.env.NAME \u5F15\u7528\u3002", "{}"],
129
- ["cwd", "stdio \u5DE5\u4F5C\u76EE\u5F55\uFF0C\u7559\u7A7A\u4F7F\u7528\u5BBF\u4E3B\u5DE5\u4F5C\u76EE\u5F55\u3002", '""'],
130
- ["url", "Streamable HTTP \u5730\u5740\u3002", "HTTP \u5FC5\u586B"],
131
- ["headers", "HTTP \u8BF7\u6C42\u5934\uFF0C\u53EF\u5F15\u7528\u73AF\u5883\u53D8\u91CF\u3002", "{}"],
132
- ["toolCallTimeoutMs", "\u5355\u6B21\u5DE5\u5177\u8C03\u7528\u8D85\u65F6\uFF0C\u5355\u4F4D\u6BEB\u79D2\u3002", "60000"],
133
- ["failOnStartupError", "\u521D\u59CB\u5316\u5931\u8D25\u65F6\u662F\u5426\u8BA9\u63D2\u4EF6\u52A0\u8F7D\u5931\u8D25\u3002", "false"],
134
- ["reconnect.enabled", "\u8FDE\u63A5\u4E22\u5931\u540E\u81EA\u52A8\u91CD\u8FDE\u3002", "true"],
135
- ["reconnect.initialDelayMs", "\u9996\u6B21\u91CD\u8FDE\u7B49\u5F85\u6BEB\u79D2\u6570\uFF0C\u4E4B\u540E\u6307\u6570\u9012\u589E\u3002", "500"],
136
- ["reconnect.maxDelayMs", "\u91CD\u8FDE\u7B49\u5F85\u4E0A\u9650\uFF0C\u5355\u4F4D\u6BEB\u79D2\u3002", "30000"],
137
- ["reconnect.maxAttempts", "\u4E00\u6B21\u8FDE\u63A5\u6545\u969C\u7684\u8FDE\u7EED\u91CD\u8BD5\u6B21\u6570\u4E0A\u9650\u3002", "10"]
138
- ];
139
-
140
- // src/client/market.css
141
- var market_default = "/* Shared page layout; interactive controls remain DSH primitives. */\n.hub-market {\n box-sizing: border-box;\n --red: #596579;\n --ink: var(--dsw-alias-label-primary, #25272b);\n --muted: var(--dsw-alias-label-tertiary, #7b7e86);\n --line: var(--dsw-alias-border-l4, #e9e9ed);\n color: var(--ink);\n font: var(--dsw-font-xs-13);\n padding: 5px 3px 20px;\n width: 100%;\n position: relative;\n isolation: isolate;\n container-type: inline-size;\n}\n.hub-topline { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 20px; }\n.hub-brand { display: flex; align-items: center; gap: 8px; font-size: 10px; letter-spacing: 1px; font-weight: 700; color: var(--muted); }\n.hub-brandmark { display: grid; place-items: center; width: 25px; height: 25px; color: white; background: var(--red); border-radius: 50%; letter-spacing: 0; }\n.hub-divider { width: 1px; height: 13px; background: var(--line); margin: 0 3px; }\n.hub-header { display: flex; justify-content: space-between; align-items: center; gap: 18px; margin-bottom: 18px; }\n.hub-eyebrow { font-size: 9px; letter-spacing: 1.9px; color: var(--red); font-weight: 700; }\n.hub-header h1 { font-size: 30px; letter-spacing: -1px; margin: 4px 0 5px; font-weight: 650; line-height: 1.4; }\n.hub-header h1 span { color: var(--red); }\n.hub-header p { margin: 0; color: var(--muted); font-size: 12px; }\n.hub-header-art { position: relative; display: grid; place-items: center; color: var(--red); background: linear-gradient(145deg, color-mix(in srgb, var(--red) 5%, var(--dsw-alias-bg-layer-2)), color-mix(in srgb, var(--red) 14%, var(--dsw-alias-bg-layer-2))); width: 80px; height: 74px; border-radius: 20px; transform: rotate(-8deg); flex-shrink: 0; border: 1px solid color-mix(in srgb, var(--red) 15%, transparent); }\n.hub-art-plus { position: absolute; right: -5px; bottom: 6px; background: var(--dsw-alias-bg-layer-3); box-shadow: 0 3px 9px #47172515; border-radius: 9px; width: 28px; height: 28px; text-align: center; font-size: 21px; line-height: 26px; transform: rotate(8deg); }\n.hub-art-dot { position: absolute; width: 8px; height: 8px; background: color-mix(in srgb, var(--red) 40%, var(--dsw-alias-bg-layer-3)); border-radius: 50%; top: 9px; left: -6px; }\n.hub-tabs { display: flex; flex-wrap: wrap; gap: 8px; padding-bottom: 12px; border-bottom: 1px solid var(--line); margin-bottom: 14px; }\n.hub-tab-count { font-size: 10px; color: var(--muted); margin-left: 5px; }\n.hub-toolbar { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }\n.hub-search-input { flex: 1; min-width: 180px; }\n.hub-results { display: flex; justify-content: space-between; align-items: center; gap: 10px; color: var(--muted); font-size: 11px; margin: 20px 0 12px; }\n.hub-results b { margin-left: 4px; font-weight: 500; }\n.hub-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 13px; }\n.hub-card { border: 1px solid var(--line); border-radius: 11px; padding: 16px; background: var(--dsw-alias-bg-layer-2, #fff); display: flex; flex-direction: column; min-width: 0; }\n.hub-description { font-size: 12px; color: var(--muted); line-height: 1.85; min-height: 44px; margin: 13px 0 12px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }\n@container (max-width: 470px) { .hub-grid { grid-template-columns: 1fr; } .hub-topline { flex-wrap: wrap; } .hub-header-art { display: none; } }\n";
116
+ // src/client/mcp.jsx
117
+ var import_react2 = __toESM(require("react"), 1);
118
+ var import_dsh_client_ui_primitives2 = require("@deepseek-ai/dsh-client-ui-primitives");
142
119
 
143
- // src/client/resources.jsx
144
- function ItemIcon({ row }) {
145
- const [failed, setFailed] = (0, import_react.useState)(false);
146
- const label = row.name.split("/").pop().replace(/^@/, "").slice(0, 1).toLocaleUpperCase();
147
- return /* @__PURE__ */ import_react.default.createElement("span", { className: "hub-item-icon", "aria-hidden": "true" }, row.icon && !failed ? /* @__PURE__ */ import_react.default.createElement("img", { src: row.icon, alt: "", referrerPolicy: "no-referrer", onError: () => setFailed(true) }) : label);
148
- }
149
- function ScopePicker({ data, scope, setScope, t, disabled }) {
150
- const [open, setOpen] = (0, import_react.useState)(false);
151
- const items = [{ id: "", label: t("\u5168\u5C40") }, ...(data?.workspaces ?? []).map((row) => ({ id: row.id, label: row.title }))];
152
- return /* @__PURE__ */ import_react.default.createElement(
153
- import_dsh_client_ui_primitives.Menu,
154
- {
155
- open,
156
- onClose: () => setOpen(false),
157
- items,
158
- selectedId: scope,
159
- portal: true,
160
- align: "end",
161
- onSelect: (id) => {
162
- setOpen(false);
163
- setScope(id);
164
- },
165
- anchor: /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "md", disabled, "aria-label": t("\u8D44\u6E90\u8303\u56F4"), "aria-expanded": open, "aria-haspopup": "menu", onClick: () => setOpen(!open) }, items.find((row) => row.id === scope)?.label ?? t("\u5168\u5C40"))
166
- }
167
- );
168
- }
120
+ // src/client/resource-modal.jsx
121
+ var import_react = __toESM(require("react"), 1);
122
+ var import_dsh_client_ui_primitives = require("@deepseek-ai/dsh-client-ui-primitives");
169
123
  function ResourceModal(props) {
170
124
  (0, import_react.useEffect)(() => {
171
125
  if (!props.open) return;
@@ -180,23 +134,52 @@ function ResourceModal(props) {
180
134
  }, [props.open, props.onClose]);
181
135
  return /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Modal, { ...props });
182
136
  }
137
+
138
+ // src/client/mcp-help.js
139
+ var mcpDescriptions = {
140
+ "serverName": "\u5DE5\u5177\u547D\u540D\u7A7A\u95F4\uFF0C1\u201332 \u4F4D\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u6216\u8FDE\u5B57\u7B26\u3002",
141
+ "transport": "stdio \u4E3A\u672C\u5730\u8FDB\u7A0B\uFF1Bstreamable-http \u4E3A HTTP \u670D\u52A1\u3002",
142
+ "command": "stdio \u53EF\u6267\u884C\u6587\u4EF6\uFF1BWindows \u53EF\u4F7F\u7528 node.exe \u7684\u5B8C\u6574\u8DEF\u5F84\u3002",
143
+ "args": "\u4F20\u7ED9\u53EF\u6267\u884C\u6587\u4EF6\u7684\u5B57\u7B26\u4E32\u6570\u7EC4\uFF0C\u4E0D\u7ECF\u8FC7 shell\u3002",
144
+ "env": "\u663E\u5F0F\u4F20\u7ED9\u670D\u52A1\u7684\u73AF\u5883\u53D8\u91CF\uFF0C\u53EF\u7528 !!js process.env.NAME \u5F15\u7528\u3002",
145
+ "cwd": "stdio \u5DE5\u4F5C\u76EE\u5F55\uFF0C\u7559\u7A7A\u4F7F\u7528\u5BBF\u4E3B\u5DE5\u4F5C\u76EE\u5F55\u3002",
146
+ "url": "Streamable HTTP \u5730\u5740\u3002",
147
+ "headers": "HTTP \u8BF7\u6C42\u5934\uFF0C\u53EF\u5F15\u7528\u73AF\u5883\u53D8\u91CF\u3002",
148
+ "toolCallTimeoutMs": "\u5355\u6B21\u5DE5\u5177\u8C03\u7528\u6216\u8D44\u6E90\u8BF7\u6C42\u8D85\u65F6\uFF0C\u5355\u4F4D\u6BEB\u79D2\u3002",
149
+ "maxInstructionBytes": "\u670D\u52A1\u7AEF\u6307\u4EE4\u7684 UTF-8 \u5B57\u8282\u4E0A\u9650\uFF0C\u5305\u542B\u670D\u52A1\u540D\u79F0\u524D\u7F00\uFF1B\u8D85\u51FA\u65F6\u8FDE\u63A5\u5931\u8D25\u3002",
150
+ "failOnStartupError": "\u521D\u59CB\u5316\u5931\u8D25\u65F6\u662F\u5426\u8BA9\u63D2\u4EF6\u52A0\u8F7D\u5931\u8D25\u3002",
151
+ "reconnect.enabled": "\u8FDE\u63A5\u4E22\u5931\u540E\u81EA\u52A8\u91CD\u8FDE\u3002",
152
+ "reconnect.initialDelayMs": "\u9996\u6B21\u91CD\u8FDE\u7B49\u5F85\u6BEB\u79D2\u6570\uFF0C\u4E4B\u540E\u6307\u6570\u9012\u589E\u3002",
153
+ "reconnect.maxDelayMs": "\u91CD\u8FDE\u7B49\u5F85\u4E0A\u9650\uFF0C\u5355\u4F4D\u6BEB\u79D2\u3002",
154
+ "reconnect.maxAttempts": "\u4E00\u6B21\u8FDE\u63A5\u6545\u969C\u7684\u8FDE\u7EED\u91CD\u8BD5\u6B21\u6570\u4E0A\u9650\u3002"
155
+ };
156
+
157
+ // src/client/mcp.jsx
183
158
  function McpEditor({ row, t, busy, save, close, request, profile }) {
184
- const [format, setFormat] = (0, import_react.useState)("json");
185
- const [help, setHelp] = (0, import_react.useState)(false);
186
- const [text, setText] = (0, import_react.useState)(JSON.stringify({ serverName: "my_server", transport: "stdio", command: "npx", args: ["-y", "mcp-server-package"] }, null, 2));
187
- const [loading, setLoading] = (0, import_react.useState)(Boolean(row));
188
- const [error, setError] = (0, import_react.useState)("");
189
- const [ready, setReady] = (0, import_react.useState)(!row);
190
- (0, import_react.useEffect)(() => {
191
- if (!row) return;
159
+ const [format, setFormat] = (0, import_react2.useState)("json");
160
+ const [help, setHelp] = (0, import_react2.useState)(false);
161
+ const [fields, setFields] = (0, import_react2.useState)([]);
162
+ const [text, setText] = (0, import_react2.useState)(JSON.stringify({ serverName: "my_server", transport: "stdio", command: "npx", args: ["-y", "mcp-server-package"] }, null, 2));
163
+ const [loading, setLoading] = (0, import_react2.useState)(true);
164
+ const [error, setError] = (0, import_react2.useState)("");
165
+ const [ready, setReady] = (0, import_react2.useState)(false);
166
+ (0, import_react2.useEffect)(() => {
192
167
  const controller = new AbortController();
193
- request("mcp-config", { id: row.id, revision: row.revision, profile, format: "json" }, { signal: controller.signal }).then((result) => {
168
+ setLoading(true);
169
+ Promise.all([
170
+ request("mcp-schema", { profile }, { signal: controller.signal }),
171
+ row ? request("mcp-config", { id: row.id, revision: row.revision, profile, format: "json" }, { signal: controller.signal }) : Promise.resolve(null)
172
+ ]).then(([schema, config]) => {
194
173
  if (!controller.signal.aborted) {
195
- setText(result.text);
174
+ setFields(schema.fields);
175
+ if (config) setText(config.text);
196
176
  setReady(true);
197
177
  }
198
178
  }).catch((error2) => {
199
- if (!controller.signal.aborted) setError(error2.message);
179
+ if (!controller.signal.aborted) {
180
+ setReady(false);
181
+ setError(error2.message);
182
+ }
200
183
  }).finally(() => {
201
184
  if (!controller.signal.aborted) setLoading(false);
202
185
  });
@@ -225,7 +208,7 @@ function McpEditor({ row, t, busy, save, close, request, profile }) {
225
208
  }
226
209
  };
227
210
  const blocked = busy || loading;
228
- return /* @__PURE__ */ import_react.default.createElement(
211
+ return /* @__PURE__ */ import_react2.default.createElement(
229
212
  ResourceModal,
230
213
  {
231
214
  open: true,
@@ -235,13 +218,13 @@ function McpEditor({ row, t, busy, save, close, request, profile }) {
235
218
  closeLabel: t("\u5173\u95ED"),
236
219
  onClose: blocked ? () => {
237
220
  } : close,
238
- footer: /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-resource-actions" }, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "md", disabled: blocked, onClick: close }, t("\u53D6\u6D88")), /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "md", variant: "primary", type: "submit", form: "hub-mcp-editor", disabled: blocked || !ready }, t("\u4FDD\u5B58")))
221
+ footer: /* @__PURE__ */ import_react2.default.createElement("div", { className: "hub-resource-actions" }, /* @__PURE__ */ import_react2.default.createElement(import_dsh_client_ui_primitives2.Button, { size: "md", disabled: blocked, onClick: close }, t("\u53D6\u6D88")), /* @__PURE__ */ import_react2.default.createElement(import_dsh_client_ui_primitives2.Button, { size: "md", variant: "primary", type: "submit", form: "hub-mcp-editor", disabled: blocked || !ready }, t("\u4FDD\u5B58")))
239
222
  },
240
- /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-editor-columns" }, /* @__PURE__ */ import_react.default.createElement("form", { id: "hub-mcp-editor", className: "hub-resource-form", onSubmit: submit }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-resource-actions", role: "group", "aria-label": t("\u914D\u7F6E\u683C\u5F0F") }, ["json", "yaml"].map((value) => /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { key: value, size: "md", className: "hub-resource-tab", "aria-pressed": format === value, disabled: blocked || !ready, onClick: () => {
223
+ /* @__PURE__ */ import_react2.default.createElement("div", { className: "hub-editor-columns" }, /* @__PURE__ */ import_react2.default.createElement("form", { id: "hub-mcp-editor", className: "hub-resource-form", onSubmit: submit }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "hub-resource-actions", role: "group", "aria-label": t("\u914D\u7F6E\u683C\u5F0F") }, ["json", "yaml"].map((value) => /* @__PURE__ */ import_react2.default.createElement(import_dsh_client_ui_primitives2.Button, { key: value, size: "md", className: "hub-resource-tab", "aria-pressed": format === value, disabled: blocked || !ready, onClick: () => {
241
224
  void convert(value);
242
- } }, value.toUpperCase())), /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "md", disabled: blocked || !ready, onClick: () => {
225
+ } }, value.toUpperCase())), /* @__PURE__ */ import_react2.default.createElement(import_dsh_client_ui_primitives2.Button, { size: "md", disabled: blocked || !ready, onClick: () => {
243
226
  void convert(format);
244
- } }, t("\u683C\u5F0F\u5316"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-resource-editor-note" }, t("\u63A8\u8350\u5728\u542F\u52A8 DSH \u7684\u73AF\u5883\u4E2D\u8BBE\u7F6E\u51ED\u636E\u3002YAML \u4F7F\u7528"), " ", /* @__PURE__ */ import_react.default.createElement("code", null, "!!js process.env.MCP_TOKEN"), t("\uFF1BJSON \u4F7F\u7528"), " ", /* @__PURE__ */ import_react.default.createElement("code", null, '{"__jsExpr":"process.env.MCP_TOKEN"}'), /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "md", "aria-expanded": help, onClick: () => setHelp(!help) }, t("\u5E2E\u52A9"))), loading && /* @__PURE__ */ import_react.default.createElement("p", { role: "status" }, t("\u6B63\u5728\u8BFB\u53D6\u914D\u7F6E")), /* @__PURE__ */ import_react.default.createElement("label", { className: "hub-resource-field" }, t("MCP \u914D\u7F6E"), /* @__PURE__ */ import_react.default.createElement("textarea", { className: "hub-resource-code", value: ready ? text : "", onChange: (event) => setText(event.target.value), disabled: blocked || !ready, spellCheck: false, autoCapitalize: "off", autoComplete: "off", maxLength: 24e3, required: true })), error && /* @__PURE__ */ import_react.default.createElement("p", { role: "alert" }, t(error))), help && /* @__PURE__ */ import_react.default.createElement("aside", { className: "hub-editor-help" }, /* @__PURE__ */ import_react.default.createElement("h3", null, t("MCP \u914D\u7F6E\u5E2E\u52A9")), /* @__PURE__ */ import_react.default.createElement("p", null, "macOS / Linux: ", /* @__PURE__ */ import_react.default.createElement("code", null, "export MCP_TOKEN=\u2026"), /* @__PURE__ */ import_react.default.createElement("br", null), "Windows PowerShell: ", /* @__PURE__ */ import_react.default.createElement("code", null, "$env:MCP_TOKEN=\u2026")), /* @__PURE__ */ import_react.default.createElement("p", null, t("\u4E0B\u5217\u9ED8\u8BA4\u503C\u7531 DSH \u63D0\u4F9B\uFF1B\u5207\u6362\u683C\u5F0F\u4E0D\u4F1A\u8865\u5165\u8FD9\u4E9B\u5B57\u6BB5\u3002")), /* @__PURE__ */ import_react.default.createElement("dl", null, mcpFields.map(([key, description, fallback]) => /* @__PURE__ */ import_react.default.createElement("div", { key }, /* @__PURE__ */ import_react.default.createElement("dt", null, /* @__PURE__ */ import_react.default.createElement("code", null, key)), /* @__PURE__ */ import_react.default.createElement("dd", null, t(description), /* @__PURE__ */ import_react.default.createElement("span", null, t(fallback.includes("\u5FC5\u586B") ? "\u8981\u6C42" : "\u9ED8\u8BA4\u503C"), " \xB7 ", fallback.includes("\u5FC5\u586B") ? t(fallback) : fallback)))))))
227
+ } }, t("\u683C\u5F0F\u5316"))), /* @__PURE__ */ import_react2.default.createElement("div", { className: "hub-resource-editor-note" }, t("\u63A8\u8350\u5728\u542F\u52A8 DSH \u7684\u73AF\u5883\u4E2D\u8BBE\u7F6E\u51ED\u636E\u3002YAML \u4F7F\u7528"), " ", /* @__PURE__ */ import_react2.default.createElement("code", null, "!!js process.env.MCP_TOKEN"), t("\uFF1BJSON \u4F7F\u7528"), " ", /* @__PURE__ */ import_react2.default.createElement("code", null, '{"__jsExpr":"process.env.MCP_TOKEN"}'), /* @__PURE__ */ import_react2.default.createElement(import_dsh_client_ui_primitives2.Button, { size: "md", "aria-expanded": help, onClick: () => setHelp(!help) }, t("\u5E2E\u52A9"))), loading && /* @__PURE__ */ import_react2.default.createElement("p", { role: "status" }, t("\u6B63\u5728\u8BFB\u53D6\u914D\u7F6E")), /* @__PURE__ */ import_react2.default.createElement("label", { className: "hub-resource-field" }, t("MCP \u914D\u7F6E"), /* @__PURE__ */ import_react2.default.createElement("textarea", { className: "hub-resource-code", value: ready ? text : "", onChange: (event) => setText(event.target.value), disabled: blocked || !ready, spellCheck: false, autoCapitalize: "off", autoComplete: "off", maxLength: 24e3, required: true })), error && /* @__PURE__ */ import_react2.default.createElement("p", { role: "alert" }, t(error))), help && /* @__PURE__ */ import_react2.default.createElement("aside", { className: "hub-editor-help" }, /* @__PURE__ */ import_react2.default.createElement("h3", null, t("MCP \u914D\u7F6E\u5E2E\u52A9")), /* @__PURE__ */ import_react2.default.createElement("p", null, "macOS / Linux: ", /* @__PURE__ */ import_react2.default.createElement("code", null, "export MCP_TOKEN=\u2026"), /* @__PURE__ */ import_react2.default.createElement("br", null), "Windows PowerShell: ", /* @__PURE__ */ import_react2.default.createElement("code", null, "$env:MCP_TOKEN=\u2026")), /* @__PURE__ */ import_react2.default.createElement("p", null, t("\u4E0B\u5217\u9ED8\u8BA4\u503C\u7531 DSH \u63D0\u4F9B\uFF1B\u5207\u6362\u683C\u5F0F\u4E0D\u4F1A\u8865\u5165\u8FD9\u4E9B\u5B57\u6BB5\u3002")), /* @__PURE__ */ import_react2.default.createElement("dl", null, fields.map((field) => /* @__PURE__ */ import_react2.default.createElement("div", { key: field.key + field.transports.join() }, /* @__PURE__ */ import_react2.default.createElement("dt", null, /* @__PURE__ */ import_react2.default.createElement("code", null, field.key), field.transports.length === 1 && /* @__PURE__ */ import_react2.default.createElement(import_dsh_client_ui_primitives2.Tag, { tone: "neutral" }, field.transports[0])), /* @__PURE__ */ import_react2.default.createElement("dd", null, t(mcpDescriptions[field.key] ?? "\u7531\u5F53\u524D DSH \u63D0\u4F9B\u7684\u914D\u7F6E\u9879\u3002"), /* @__PURE__ */ import_react2.default.createElement("span", null, t(field.required ? "\u8981\u6C42" : "\u9ED8\u8BA4\u503C"), " \xB7 ", field.required ? t("\u5FC5\u586B") : Object.hasOwn(field, "default") ? JSON.stringify(field.default) : t("\u672A\u63D0\u4F9B\u9ED8\u8BA4\u503C"))))))))
245
228
  );
246
229
  }
247
230
  function McpSummary({ row, t, activation }) {
@@ -255,33 +238,82 @@ function McpSummary({ row, t, activation }) {
255
238
  }
256
239
  }
257
240
  const loaded = activation(row.activation);
258
- return /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-mcp-summary" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-mcp-statuses" }, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Tooltip, { label: `${t("\u52A0\u8F7D\u72B6\u6001")} \xB7 ${loaded}` }, /* @__PURE__ */ import_react.default.createElement("span", { tabIndex: 0 }, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Tag, { tone: { active: "success", loading: "info", unloading: "info", failed: "danger" }[row.activation] ?? "neutral" }, loaded === t("\u672A\u77E5") ? t("\u52A0\u8F7D\u672A\u77E5") : loaded))), /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Tooltip, { label: t("\u5BBF\u4E3B\u672A\u516C\u5F00\u6301\u7EED\u8FDE\u63A5\u72B6\u6001\uFF1B\u5DF2\u52A0\u8F7D\u4E0D\u4EE3\u8868\u8FDE\u63A5\u6210\u529F\u3002") }, /* @__PURE__ */ import_react.default.createElement("span", { tabIndex: 0 }, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Tag, { tone: "neutral" }, t("\u8FDE\u63A5\u672A\u77E5"))))), /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Tooltip, { label: `${row.transport}${endpoint ? " \xB7 " + endpoint : ""}` }, /* @__PURE__ */ import_react.default.createElement("span", { className: "hub-mcp-endpoint", tabIndex: 0 }, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Tag, { tone: "neutral" }, row.transport === "streamable-http" ? "HTTP" : row.transport === "unknown" ? t("\u672A\u77E5") : row.transport), endpointLabel && /* @__PURE__ */ import_react.default.createElement("code", null, endpointLabel))));
241
+ return /* @__PURE__ */ import_react2.default.createElement("div", { className: "hub-mcp-summary" }, /* @__PURE__ */ import_react2.default.createElement("div", { className: "hub-mcp-statuses" }, /* @__PURE__ */ import_react2.default.createElement(import_dsh_client_ui_primitives2.Tooltip, { label: `${t("\u52A0\u8F7D\u72B6\u6001")} \xB7 ${loaded}` }, /* @__PURE__ */ import_react2.default.createElement("span", { tabIndex: 0 }, /* @__PURE__ */ import_react2.default.createElement(import_dsh_client_ui_primitives2.Tag, { tone: { active: "success", loading: "info", unloading: "info", failed: "danger" }[row.activation] ?? "neutral" }, loaded === t("\u672A\u77E5") ? t("\u52A0\u8F7D\u672A\u77E5") : loaded))), /* @__PURE__ */ import_react2.default.createElement(import_dsh_client_ui_primitives2.Tooltip, { label: t("\u5BBF\u4E3B\u672A\u516C\u5F00\u6301\u7EED\u8FDE\u63A5\u72B6\u6001\uFF1B\u5DF2\u52A0\u8F7D\u4E0D\u4EE3\u8868\u8FDE\u63A5\u6210\u529F\u3002") }, /* @__PURE__ */ import_react2.default.createElement("span", { tabIndex: 0 }, /* @__PURE__ */ import_react2.default.createElement(import_dsh_client_ui_primitives2.Tag, { tone: "neutral" }, t("\u8FDE\u63A5\u672A\u77E5"))))), /* @__PURE__ */ import_react2.default.createElement(import_dsh_client_ui_primitives2.Tooltip, { label: `${row.transport}${endpoint ? " \xB7 " + endpoint : ""}` }, /* @__PURE__ */ import_react2.default.createElement("span", { className: "hub-mcp-endpoint", tabIndex: 0 }, /* @__PURE__ */ import_react2.default.createElement(import_dsh_client_ui_primitives2.Tag, { tone: "neutral" }, row.transport === "streamable-http" ? "HTTP" : row.transport === "unknown" ? t("\u672A\u77E5") : row.transport), endpointLabel && /* @__PURE__ */ import_react2.default.createElement("code", null, endpointLabel))));
242
+ }
243
+
244
+ // src/client/entry.jsx
245
+ var import_react3 = __toESM(require("react"), 1);
246
+ var import_dsh_client_ui_primitives3 = require("@deepseek-ai/dsh-client-ui-primitives");
247
+ var css = `
248
+ .hub-entry-button { width: 32px; height: 32px; padding: 0; flex: none; }
249
+ .hub-entry-fallback { position: absolute; top: 16px; right: 24px; }
250
+ body:has(.hub-header-entry) .hub-entry-fallback { display: none; }
251
+ `;
252
+ function HubIconButton({ label, icon = /* @__PURE__ */ import_react3.default.createElement(import_dsh_client_ui_primitives3.IconCordisPluginOutline14, { size: 18 }), onClick }) {
253
+ return /* @__PURE__ */ import_react3.default.createElement(import_react3.default.Fragment, null, /* @__PURE__ */ import_react3.default.createElement("style", null, css), /* @__PURE__ */ import_react3.default.createElement(import_dsh_client_ui_primitives3.Tooltip, { label, side: "bottom" }, /* @__PURE__ */ import_react3.default.createElement(import_dsh_client_ui_primitives3.Button, { className: "hub-entry-button", "aria-label": label, icon, onClick })));
254
+ }
255
+ function HubHeaderEntry({ ctx, panelId, brand }) {
256
+ return /* @__PURE__ */ import_react3.default.createElement("span", { className: "hub-header-entry" }, /* @__PURE__ */ import_react3.default.createElement(HubIconButton, { label: brand.navTitle, onClick: () => ctx.layout.selectPanel(panelId) }));
257
+ }
258
+ function HubEmptyEntry({ usePanelInfo, ctx, panelId, brand }) {
259
+ const conversation = usePanelInfo((info) => info.activePanelId === null);
260
+ return conversation ? /* @__PURE__ */ import_react3.default.createElement("div", { className: "hub-entry-fallback" }, /* @__PURE__ */ import_react3.default.createElement(HubIconButton, { label: brand.navTitle, onClick: () => ctx.layout.selectPanel(panelId) })) : null;
261
+ }
262
+
263
+ // src/client/market.css
264
+ var market_default = "/* Shared page layout; interactive controls remain DSH primitives. */\n.hub-market {\n box-sizing: border-box;\n --red: #596579;\n --ink: var(--dsw-alias-label-primary, #25272b);\n --muted: var(--dsw-alias-label-tertiary, #7b7e86);\n --line: var(--dsw-alias-border-l4, #e9e9ed);\n color: var(--ink);\n font: var(--dsw-font-xs-13);\n padding: 5px 3px 20px;\n width: 100%;\n position: relative;\n isolation: isolate;\n container-type: inline-size;\n}\n.hub-topline { display: flex; align-items: center; justify-content: space-between; gap: 10px; margin-bottom: 20px; }\n.hub-brand { display: flex; align-items: center; gap: 8px; font-size: 10px; letter-spacing: 1px; font-weight: 700; color: var(--muted); }\n.hub-brandmark { display: grid; place-items: center; width: 25px; height: 25px; color: white; background: var(--red); border-radius: 50%; letter-spacing: 0; }\n.hub-divider { width: 1px; height: 13px; background: var(--line); margin: 0 3px; }\n.hub-header { display: flex; justify-content: space-between; align-items: center; gap: 18px; margin-bottom: 18px; }\n.hub-eyebrow { font-size: 9px; letter-spacing: 1.9px; color: var(--red); font-weight: 700; }\n.hub-header h1 { font-size: 30px; letter-spacing: -1px; margin: 4px 0 5px; font-weight: 650; line-height: 1.4; }\n.hub-header h1 span { color: var(--red); }\n.hub-header p { margin: 0; color: var(--muted); font-size: 12px; }\n.hub-header-art { position: relative; display: grid; place-items: center; color: var(--red); background: linear-gradient(145deg, color-mix(in srgb, var(--red) 5%, var(--dsw-alias-bg-layer-2)), color-mix(in srgb, var(--red) 14%, var(--dsw-alias-bg-layer-2))); width: 80px; height: 74px; border-radius: 20px; transform: rotate(-8deg); flex-shrink: 0; border: 1px solid color-mix(in srgb, var(--red) 15%, transparent); }\n.hub-art-plus { position: absolute; right: -5px; bottom: 6px; background: var(--dsw-alias-bg-layer-3); box-shadow: 0 3px 9px #47172515; border-radius: 9px; width: 28px; height: 28px; text-align: center; font-size: 21px; line-height: 26px; transform: rotate(8deg); }\n.hub-art-dot { position: absolute; width: 8px; height: 8px; background: color-mix(in srgb, var(--red) 40%, var(--dsw-alias-bg-layer-3)); border-radius: 50%; top: 9px; left: -6px; }\n.hub-tabs { display: flex; flex-wrap: wrap; gap: 8px; padding-bottom: 12px; border-bottom: 1px solid var(--line); margin-bottom: 14px; }\n.hub-tab-count { font-size: 10px; color: var(--muted); margin-left: 5px; }\n.hub-toolbar { display: flex; align-items: center; flex-wrap: wrap; gap: 8px; }\n.hub-search-input { flex: 1; min-width: 180px; }\n.hub-results { display: flex; justify-content: space-between; align-items: center; gap: 10px; color: var(--muted); font-size: 11px; margin: 20px 0 12px; }\n.hub-results b { margin-left: 4px; font-weight: 500; }\n.hub-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 13px; }\n.hub-card { border: 1px solid var(--line); border-radius: 11px; padding: 16px; background: var(--dsw-alias-bg-layer-2, #fff); display: flex; flex-direction: column; min-width: 0; }\n.hub-description { font-size: 12px; color: var(--muted); line-height: 1.85; min-height: 44px; margin: 13px 0 12px; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }\n@container (max-width: 470px) { .hub-grid { grid-template-columns: 1fr; } .hub-topline { flex-wrap: wrap; } .hub-header-art { display: none; } }\n";
265
+
266
+ // src/client/resources.jsx
267
+ function ItemIcon({ row }) {
268
+ const [failed, setFailed] = (0, import_react4.useState)(false);
269
+ const label = row.name.split("/").pop().replace(/^@/, "").slice(0, 1).toLocaleUpperCase();
270
+ return /* @__PURE__ */ import_react4.default.createElement("span", { className: "hub-item-icon", "aria-hidden": "true" }, row.icon && !failed ? /* @__PURE__ */ import_react4.default.createElement("img", { src: row.icon, alt: "", referrerPolicy: "no-referrer", onError: () => setFailed(true) }) : label);
271
+ }
272
+ function ScopePicker({ data, scope, setScope, t, disabled }) {
273
+ const [open, setOpen] = (0, import_react4.useState)(false);
274
+ const items = [{ id: "", label: t("\u5168\u5C40") }, ...(data?.workspaces ?? []).map((row) => ({ id: row.id, label: row.title }))];
275
+ return /* @__PURE__ */ import_react4.default.createElement(
276
+ import_dsh_client_ui_primitives4.Menu,
277
+ {
278
+ open,
279
+ onClose: () => setOpen(false),
280
+ items,
281
+ selectedId: scope,
282
+ portal: true,
283
+ align: "end",
284
+ onSelect: (id) => {
285
+ setOpen(false);
286
+ setScope(id);
287
+ },
288
+ anchor: /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Button, { size: "md", disabled, "aria-label": t("\u8D44\u6E90\u8303\u56F4"), "aria-expanded": open, "aria-haspopup": "menu", onClick: () => setOpen(!open) }, items.find((row) => row.id === scope)?.label ?? t("\u5168\u5C40"))
289
+ }
290
+ );
259
291
  }
260
292
  function SkillPreview({ file, t, close }) {
261
- const view = (0, import_react.useRef)(null);
293
+ const view = (0, import_react4.useRef)(null);
262
294
  const frontmatter = file.text.match(/^---\r?\n([\s\S]*?)\r?\n(?:---|\.\.\.)(?:\r?\n|$)/);
263
295
  const title = file.path.split(/[\\/]/).slice(-2, -1).join("") + "/skill.md";
264
296
  const body = frontmatter ? file.text.slice(frontmatter[0].length) : file.text;
265
- (0, import_react.useEffect)(() => {
297
+ (0, import_react4.useEffect)(() => {
266
298
  if (window.matchMedia("(max-width: 850px)").matches) view.current?.scrollIntoView({ block: "start" });
267
299
  }, [file]);
268
- return /* @__PURE__ */ import_react.default.createElement("aside", { className: "hub-skill-view", ref: view, "aria-label": title }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-resource-card-heading" }, /* @__PURE__ */ import_react.default.createElement("h2", { title }, title), /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "sm", onClick: close, icon: /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.IconCloseOutline16, null) }, t("\u5173\u95ED"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-skill-markdown" }, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.MarkdownText, { text: body, labels: { code: { copyLabel: t("\u590D\u5236"), copiedLabel: t("\u5DF2\u590D\u5236") }, footnotes: t("\u811A\u6CE8") } })));
300
+ return /* @__PURE__ */ import_react4.default.createElement("aside", { className: "hub-skill-view", ref: view, "aria-label": title }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "hub-resource-card-heading" }, /* @__PURE__ */ import_react4.default.createElement("h2", { title }, title), /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Button, { size: "sm", onClick: close, icon: /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.IconCloseOutline16, null) }, t("\u5173\u95ED"))), /* @__PURE__ */ import_react4.default.createElement("div", { className: "hub-skill-markdown" }, /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.MarkdownText, { text: body, labels: { code: { copyLabel: t("\u590D\u5236"), copiedLabel: t("\u5DF2\u590D\u5236") }, footnotes: t("\u811A\u6CE8") } })));
269
301
  }
270
302
  function ResourceHub({ ctx, request, experiments, t, brand, panelId }) {
271
- (0, import_react.useSyncExternalStore)((fn) => ctx.locale.subscribe(fn), () => ctx.locale.getSnapshot().active);
272
- const [tab, setTab] = (0, import_react.useState)("skills");
273
- const [scope, setScope] = (0, import_react.useState)("");
274
- const [file, setFile] = (0, import_react.useState)(null);
275
- const [desktopHelp, setDesktopHelp] = (0, import_react.useState)(false);
276
- const [data, setData] = (0, import_react.useState)(null);
277
- const [query, setQuery] = (0, import_react.useState)("");
278
- const [error, setError] = (0, import_react.useState)("");
279
- const [busy, setBusy] = (0, import_react.useState)(false);
280
- const [editor, setEditor] = (0, import_react.useState)(null);
281
- const [deleting, setDeleting] = (0, import_react.useState)(null);
282
- const generation = (0, import_react.useRef)(0);
283
- const mounted = (0, import_react.useRef)(true);
284
- (0, import_react.useEffect)(() => {
303
+ (0, import_react4.useSyncExternalStore)((fn) => ctx.locale.subscribe(fn), () => ctx.locale.getSnapshot().active);
304
+ const [tab, setTab] = (0, import_react4.useState)("skills");
305
+ const [scope, setScope] = (0, import_react4.useState)("");
306
+ const [file, setFile] = (0, import_react4.useState)(null);
307
+ const [desktopHelp, setDesktopHelp] = (0, import_react4.useState)(false);
308
+ const [data, setData] = (0, import_react4.useState)(null);
309
+ const [query, setQuery] = (0, import_react4.useState)("");
310
+ const [error, setError] = (0, import_react4.useState)("");
311
+ const [busy, setBusy] = (0, import_react4.useState)(false);
312
+ const [editor, setEditor] = (0, import_react4.useState)(null);
313
+ const [deleting, setDeleting] = (0, import_react4.useState)(null);
314
+ const generation = (0, import_react4.useRef)(0);
315
+ const mounted = (0, import_react4.useRef)(true);
316
+ (0, import_react4.useEffect)(() => {
285
317
  mounted.current = true;
286
318
  return () => {
287
319
  mounted.current = false;
@@ -306,7 +338,7 @@ function ResourceHub({ ctx, request, experiments, t, brand, panelId }) {
306
338
  if (!signal?.aborted && mounted.current && token === generation.current) setError(error2.message);
307
339
  }
308
340
  };
309
- (0, import_react.useEffect)(() => {
341
+ (0, import_react4.useEffect)(() => {
310
342
  const controller = new AbortController();
311
343
  setData(null);
312
344
  void refresh(controller.signal);
@@ -364,7 +396,7 @@ function ResourceHub({ ctx, request, experiments, t, brand, panelId }) {
364
396
  setQuery("");
365
397
  };
366
398
  const activation = (state) => t({ active: "\u5DF2\u52A0\u8F7D", loading: "\u52A0\u8F7D\u4E2D", pending: "\u672A\u52A0\u8F7D", disabled: "\u5DF2\u7981\u7528", failed: "\u52A0\u8F7D\u5931\u8D25", disposed: "\u672A\u52A0\u8F7D", unloading: "\u52A0\u8F7D\u4E2D" }[state] ?? "\u672A\u77E5");
367
- return /* @__PURE__ */ import_react.default.createElement("section", { className: `hub-resource-page ${file ? "with-file" : ""}`, "aria-label": brand.title }, /* @__PURE__ */ import_react.default.createElement("style", null, market_default, resources_default), /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-market hub-resource-content", style: { "--red": brand.primaryColor } }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-topline" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-brand" }, /* @__PURE__ */ import_react.default.createElement("span", { className: "hub-brandmark" }, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.IconCordisPluginOutline14, { size: 17 })), brand.title, /* @__PURE__ */ import_react.default.createElement("span", { className: "hub-divider" }), brand.subTitle), /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "md", onClick: () => ctx.layout.selectPanel(null), icon: /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.IconCloseOutline16, null) }, t("\u8FD4\u56DE\u4F1A\u8BDD"))), /* @__PURE__ */ import_react.default.createElement("header", { className: "hub-header" }, /* @__PURE__ */ import_react.default.createElement("div", null, /* @__PURE__ */ import_react.default.createElement("span", { className: "hub-eyebrow" }, "SKILLS \xB7 MCP \xB7 PLUGINS"), /* @__PURE__ */ import_react.default.createElement("h1", null, t("\u8D44\u6E90\u7BA1\u7406"), /* @__PURE__ */ import_react.default.createElement("span", null, ".")), /* @__PURE__ */ import_react.default.createElement("p", null, t("\u5728\u5F53\u524D\u5B9E\u4F8B\u4E2D\u7BA1\u7406 Skill\u3001MCP \u548C\u63D2\u4EF6\u3002"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-header-art", "aria-hidden": "true" }, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.IconCordisPluginOutline14, { size: 38 }), /* @__PURE__ */ import_react.default.createElement("span", { className: "hub-art-plus" }, "+"), /* @__PURE__ */ import_react.default.createElement("span", { className: "hub-art-dot" }))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-resource-profile" }, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Pill, null, t("\u5F53\u524D profile"), " \xB7 ", data?.profile.name ?? "\u2026"), /* @__PURE__ */ import_react.default.createElement("span", { className: "hub-resource-path", title: data?.profile.directory }, data?.profile.directory ?? "\u2026")), /* @__PURE__ */ import_react.default.createElement("div", { role: "tablist", className: "hub-tabs", "aria-label": brand.title, onKeyDown: (event) => {
399
+ return /* @__PURE__ */ import_react4.default.createElement("section", { className: `hub-resource-page ${file ? "with-file" : ""}`, "aria-label": brand.title }, /* @__PURE__ */ import_react4.default.createElement("style", null, market_default, resources_default), /* @__PURE__ */ import_react4.default.createElement("div", { className: "hub-market hub-resource-content", style: { "--red": brand.primaryColor } }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "hub-topline" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "hub-brand" }, /* @__PURE__ */ import_react4.default.createElement("span", { className: "hub-brandmark" }, /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.IconCordisPluginOutline14, { size: 17 })), brand.title, /* @__PURE__ */ import_react4.default.createElement("span", { className: "hub-divider" }), brand.subTitle), /* @__PURE__ */ import_react4.default.createElement(HubIconButton, { label: t("\u8FD4\u56DE\u4F1A\u8BDD"), icon: /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.IconCloseOutline16, { size: 18 }), onClick: () => ctx.layout.selectPanel(null) })), /* @__PURE__ */ import_react4.default.createElement("header", { className: "hub-header" }, /* @__PURE__ */ import_react4.default.createElement("div", null, /* @__PURE__ */ import_react4.default.createElement("span", { className: "hub-eyebrow" }, "SKILLS \xB7 MCP \xB7 PLUGINS"), /* @__PURE__ */ import_react4.default.createElement("h1", null, t("\u8D44\u6E90\u7BA1\u7406"), /* @__PURE__ */ import_react4.default.createElement("span", null, ".")), /* @__PURE__ */ import_react4.default.createElement("p", null, t("\u5728\u5F53\u524D\u5B9E\u4F8B\u4E2D\u7BA1\u7406 Skill\u3001MCP \u548C\u63D2\u4EF6\u3002"))), /* @__PURE__ */ import_react4.default.createElement("div", { className: "hub-header-art", "aria-hidden": "true" }, /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.IconCordisPluginOutline14, { size: 38 }), /* @__PURE__ */ import_react4.default.createElement("span", { className: "hub-art-plus" }, "+"), /* @__PURE__ */ import_react4.default.createElement("span", { className: "hub-art-dot" }))), /* @__PURE__ */ import_react4.default.createElement("div", { className: "hub-resource-profile" }, /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Pill, null, t("\u5F53\u524D profile"), " \xB7 ", data?.profile.name ?? "\u2026"), /* @__PURE__ */ import_react4.default.createElement("span", { className: "hub-resource-path", title: data?.profile.directory }, data?.profile.directory ?? "\u2026")), /* @__PURE__ */ import_react4.default.createElement("div", { role: "tablist", className: "hub-tabs", "aria-label": brand.title, onKeyDown: (event) => {
368
400
  const index = tabs.findIndex((row) => row.id === tab);
369
401
  const next = event.key === "ArrowRight" ? (index + 1) % tabs.length : event.key === "ArrowLeft" ? (index + tabs.length - 1) % tabs.length : event.key === "Home" ? 0 : event.key === "End" ? tabs.length - 1 : -1;
370
402
  if (next !== -1) {
@@ -372,34 +404,36 @@ function ResourceHub({ ctx, request, experiments, t, brand, panelId }) {
372
404
  switchTab(tabs[next].id);
373
405
  event.currentTarget.querySelectorAll('[role="tab"]')[next].focus();
374
406
  }
375
- } }, tabs.map((row) => /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { key: row.id, role: "tab", id: `${panelId}-${row.id}`, "aria-controls": `${panelId}-content`, "aria-selected": tab === row.id, tabIndex: tab === row.id ? 0 : -1, size: "md", className: "hub-resource-tab", onClick: () => switchTab(row.id) }, row.label, /* @__PURE__ */ import_react.default.createElement("span", { className: "hub-tab-count" }, data?.[row.id]?.length ?? "\u2014")))), /* @__PURE__ */ import_react.default.createElement("div", { role: "tabpanel", id: `${panelId}-content`, "aria-labelledby": `${panelId}-${tab}` }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-toolbar" }, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Input, { className: "hub-search-input", icon: /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.IconSearchOutline16, null), "aria-label": t("\u641C\u7D22\u540D\u79F0\u3001\u63CF\u8FF0\u6216\u6765\u6E90"), placeholder: t("\u641C\u7D22\u540D\u79F0\u3001\u63CF\u8FF0\u6216\u6765\u6E90"), value: query, onChange: (event) => setQuery(event.target.value) }), tab === "skills" && /* @__PURE__ */ import_react.default.createElement(ScopePicker, { data, scope, setScope, t, disabled: busy }), /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "md", disabled: busy, onClick: () => {
407
+ } }, tabs.map((row) => /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Button, { key: row.id, role: "tab", id: `${panelId}-${row.id}`, "aria-controls": `${panelId}-content`, "aria-selected": tab === row.id, tabIndex: tab === row.id ? 0 : -1, size: "md", className: "hub-resource-tab", onClick: () => switchTab(row.id) }, row.label, /* @__PURE__ */ import_react4.default.createElement("span", { className: "hub-tab-count" }, data?.[row.id]?.length ?? "\u2014")))), /* @__PURE__ */ import_react4.default.createElement("div", { role: "tabpanel", id: `${panelId}-content`, "aria-labelledby": `${panelId}-${tab}` }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "hub-toolbar" }, /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Input, { className: "hub-search-input", icon: /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.IconSearchOutline16, null), "aria-label": t("\u641C\u7D22\u540D\u79F0\u3001\u63CF\u8FF0\u6216\u6765\u6E90"), placeholder: t("\u641C\u7D22\u540D\u79F0\u3001\u63CF\u8FF0\u6216\u6765\u6E90"), value: query, onChange: (event) => setQuery(event.target.value) }), tab === "skills" && /* @__PURE__ */ import_react4.default.createElement(ScopePicker, { data, scope, setScope, t, disabled: busy }), /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Button, { size: "md", disabled: busy, onClick: () => {
376
408
  setBusy(true);
377
409
  void refresh().finally(() => {
378
410
  if (mounted.current) setBusy(false);
379
411
  });
380
- }, icon: /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.IconRefreshOutline16, null) }, t("\u5237\u65B0")), tab === "plugins" && data?.profile.installation === "desktop" && /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { onClick: () => setDesktopHelp(true) }, t("\u684C\u9762\u63D2\u4EF6\u7BA1\u7406")), tab === "mcps" && /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "md", variant: "primary", disabled: !data || busy, onClick: () => setEditor({}) }, t("\u6DFB\u52A0 MCP"))), /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-results" }, /* @__PURE__ */ import_react.default.createElement("span", null, t("\u5F53\u524D\u5217\u8868"), " ", /* @__PURE__ */ import_react.default.createElement("b", null, rows.length)), /* @__PURE__ */ import_react.default.createElement("span", { className: "hub-resource-scope-note" }, t(tab === "skills" ? "\u5F00\u5173\u53EA\u5F71\u54CD\u5F53\u524D profile\uFF1BSkill \u6E90\u6587\u4EF6\u4FDD\u6301\u539F\u6837\u3002" : tab === "mcps" ? "MCP \u4E3A\u5F53\u524D profile \u7684\u5168\u5C40\u670D\u52A1\uFF1B\u5DE5\u4F5C\u533A\u8303\u56F4\u53EA\u7B5B\u9009 Skill\u3002" : tab === "plugins" ? "\u4EC5\u5C55\u793A\u5DF2\u5B89\u88C5\u7684\u975E\u5B98\u65B9\u63D2\u4EF6" : "\u5B9E\u9A8C\u6027\u529F\u80FD"))), error && /* @__PURE__ */ import_react.default.createElement("p", { role: "alert" }, t(error)), tab === "experiments" && data?.experimentsError && /* @__PURE__ */ import_react.default.createElement("p", { role: "alert" }, t(data.experimentsError)), !data && !error && /* @__PURE__ */ import_react.default.createElement("p", { role: "status" }, t("\u6B63\u5728\u8BFB\u53D6\u8D44\u6E90")), data && tab === "skills" && !data.skillsComplete && /* @__PURE__ */ import_react.default.createElement("p", { role: "status" }, t("\u8D44\u6E90\u5C1A\u672A\u5B8C\u6574\u52A0\u8F7D\uFF0C\u8BF7\u5237\u65B0\u91CD\u8BD5")), /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-grid hub-resource-grid" }, rows.map((row) => /* @__PURE__ */ import_react.default.createElement("article", { key: row.id ?? row.name, className: "hub-card hub-resource-card" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-resource-card-heading" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-resource-identity" }, /* @__PURE__ */ import_react.default.createElement(ItemIcon, { row }), /* @__PURE__ */ import_react.default.createElement("h2", { className: "hub-resource-card-name", title: row.name }, row.name, (tab === "mcps" || tab === "plugins") && /* @__PURE__ */ import_react.default.createElement("span", { className: "hub-resource-version" }, "@", row.version || t("\u672A\u77E5")))), tab !== "plugins" && tab !== "experiments" ? /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Switch, { checked: Boolean(row.enabled), label: `${t(row.enabled ? "\u7981\u7528" : "\u542F\u7528")} ${row.name}`, disabled: busy || tab === "skills" && (!row.canToggle || !row.enabled && !row.managed), title: tab === "skills" && !row.canToggle ? t("\u6B64\u5F00\u5173\u7EE7\u627F\u81EA\u5176\u5B83\u8303\u56F4\uFF0C\u8BF7\u5207\u6362\u8303\u56F4\u4FEE\u6539") : void 0, onChange: (enabled) => run({ action: tab === "skills" ? "skill-toggle" : "mcp-toggle", id: row.id, revision: row.revision, enabled }) }) : tab === "experiments" ? /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Switch, { checked: Boolean(row.enabled), label: `${t(row.enabled ? "\u7981\u7528" : "\u542F\u7528")} ${row.name}`, disabled: busy || row.busy || !row.canToggle, onChange: (enabled) => {
412
+ }, icon: /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.IconRefreshOutline16, null) }, t("\u5237\u65B0")), tab === "plugins" && data?.profile.installation === "desktop" && /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Button, { onClick: () => setDesktopHelp(true) }, t("\u684C\u9762\u63D2\u4EF6\u7BA1\u7406")), tab === "mcps" && /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Button, { size: "md", variant: "primary", disabled: !data || busy, onClick: () => setEditor({}) }, t("\u6DFB\u52A0 MCP"))), /* @__PURE__ */ import_react4.default.createElement("div", { className: "hub-results" }, /* @__PURE__ */ import_react4.default.createElement("span", null, t("\u5F53\u524D\u5217\u8868"), " ", /* @__PURE__ */ import_react4.default.createElement("b", null, rows.length)), /* @__PURE__ */ import_react4.default.createElement("span", { className: "hub-resource-scope-note" }, t(tab === "skills" ? "\u5F00\u5173\u53EA\u5F71\u54CD\u5F53\u524D profile\uFF1BSkill \u6E90\u6587\u4EF6\u4FDD\u6301\u539F\u6837\u3002" : tab === "mcps" ? "MCP \u4E3A\u5F53\u524D profile \u7684\u5168\u5C40\u670D\u52A1\uFF1B\u5DE5\u4F5C\u533A\u8303\u56F4\u53EA\u7B5B\u9009 Skill\u3002" : tab === "plugins" ? "\u4EC5\u5C55\u793A\u5DF2\u5B89\u88C5\u7684\u975E\u5B98\u65B9\u63D2\u4EF6" : "\u5B9E\u9A8C\u6027\u529F\u80FD"))), error && /* @__PURE__ */ import_react4.default.createElement("p", { role: "alert" }, t(error)), tab === "experiments" && data?.experimentsError && /* @__PURE__ */ import_react4.default.createElement("p", { role: "alert" }, t(data.experimentsError)), !data && !error && /* @__PURE__ */ import_react4.default.createElement("p", { role: "status" }, t("\u6B63\u5728\u8BFB\u53D6\u8D44\u6E90")), data && tab === "skills" && !data.skillsComplete && /* @__PURE__ */ import_react4.default.createElement("p", { role: "status" }, t("\u8D44\u6E90\u5C1A\u672A\u5B8C\u6574\u52A0\u8F7D\uFF0C\u8BF7\u5237\u65B0\u91CD\u8BD5")), /* @__PURE__ */ import_react4.default.createElement("div", { className: "hub-grid hub-resource-grid" }, rows.map((row) => /* @__PURE__ */ import_react4.default.createElement("article", { key: row.id ?? row.name, className: "hub-card hub-resource-card" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "hub-resource-card-heading" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "hub-resource-identity" }, /* @__PURE__ */ import_react4.default.createElement(ItemIcon, { row }), /* @__PURE__ */ import_react4.default.createElement("h2", { className: "hub-resource-card-name", title: row.name }, row.name, (tab === "mcps" || tab === "plugins") && /* @__PURE__ */ import_react4.default.createElement("span", { className: "hub-resource-version" }, "@", row.version || t("\u672A\u77E5")))), tab !== "plugins" && tab !== "experiments" ? /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Switch, { checked: Boolean(row.enabled), label: `${t(row.enabled ? "\u7981\u7528" : "\u542F\u7528")} ${row.name}`, disabled: busy || tab === "skills" && (!row.canToggle || !row.enabled && !row.managed), title: tab === "skills" && !row.canToggle ? t("\u6B64\u5F00\u5173\u7EE7\u627F\u81EA\u5176\u5B83\u8303\u56F4\uFF0C\u8BF7\u5207\u6362\u8303\u56F4\u4FEE\u6539") : void 0, onChange: (enabled) => run({ action: tab === "skills" ? "skill-toggle" : "mcp-toggle", id: row.id, revision: row.revision, enabled }) }) : tab === "experiments" ? /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Switch, { checked: Boolean(row.enabled), label: `${t(row.enabled ? "\u7981\u7528" : "\u542F\u7528")} ${row.name}`, disabled: busy || row.busy || !row.canToggle, onChange: (enabled) => {
381
413
  void changeExperiment(row, enabled);
382
- } }) : null), tab === "mcps" && /* @__PURE__ */ import_react.default.createElement(McpSummary, { row, t, activation }), /* @__PURE__ */ import_react.default.createElement("p", { className: "hub-description", title: tab === "experiments" ? t(row.description) : row.description }, (tab === "experiments" ? t(row.description) : row.description) || t(tab === "mcps" ? "\u670D\u52A1\u5C1A\u672A\u63D0\u4F9B\u63CF\u8FF0\uFF0C\u53EF\u6D4B\u8BD5\u8FDE\u63A5\u8BFB\u53D6\u3002" : "\u6682\u65E0\u63CF\u8FF0")), tab === "mcps" && /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, row.probe && /* @__PURE__ */ import_react.default.createElement("p", { className: "hub-resource-meta" }, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Pill, null, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.StateDot, { state: row.probe.status === "success" ? "done" : "error" }), " ", t(row.probe.status === "success" ? "\u6D4B\u8BD5\u6210\u529F" : "\u6D4B\u8BD5\u5931\u8D25"), " \xB7 ", new Date(row.probe.at).toLocaleTimeString(), row.probe.tools !== void 0 && ` \xB7 ${t("\u5DE5\u5177\u6570")} ${row.probe.tools}`))), tab === "experiments" && /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-resource-actions" }, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Pill, null, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.StateDot, { state: row.pendingRestart ? "warning" : row.activeEnabled ? "done" : "idle" }), t(row.busy || busy ? "\u5B9E\u9A8C\u6027\u529F\u80FD\u6B63\u5728\u66F4\u65B0" : row.pendingRestart ? "\u91CD\u542F DSH \u540E\u751F\u6548" : row.activeEnabled === null ? "\u8FD0\u884C\u72B6\u6001\u672A\u77E5" : row.activeEnabled ? "\u5DF2\u542F\u7528" : "\u5DF2\u7981\u7528")), row.pendingRestart && /* @__PURE__ */ import_react.default.createElement("span", null, t(row.enabled ? "\u914D\u7F6E\u5DF2\u542F\u7528" : "\u914D\u7F6E\u5DF2\u7981\u7528")), !row.supported && /* @__PURE__ */ import_react.default.createElement("span", null, t("\u5F53\u524D\u5BBF\u4E3B\u4E0D\u652F\u6301\u7BA1\u7406\u6B64\u5B9E\u9A8C\u6027\u529F\u80FD")), !row.installed && row.canInstall && /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "sm", disabled: busy || row.busy, onClick: () => {
414
+ } }) : null), tab === "mcps" && /* @__PURE__ */ import_react4.default.createElement(McpSummary, { row, t, activation }), /* @__PURE__ */ import_react4.default.createElement("p", { className: "hub-description", title: tab === "experiments" ? t(row.description) : row.description }, (tab === "experiments" ? t(row.description) : row.description) || t(tab === "mcps" ? row.probe?.status === "success" ? "\u670D\u52A1\u672A\u63D0\u4F9B\u63CF\u8FF0\u3002" : "\u670D\u52A1\u5C1A\u672A\u63D0\u4F9B\u63CF\u8FF0\uFF0C\u53EF\u6D4B\u8BD5\u8FDE\u63A5\u8BFB\u53D6\u3002" : "\u6682\u65E0\u63CF\u8FF0")), tab === "mcps" && /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, null, row.probe && /* @__PURE__ */ import_react4.default.createElement("p", { className: "hub-resource-meta" }, /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Pill, null, /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.StateDot, { state: row.probe.status === "success" ? "done" : "error" }), " ", t(row.probe.status === "success" ? "\u6D4B\u8BD5\u6210\u529F" : "\u6D4B\u8BD5\u5931\u8D25"), " \xB7 ", new Date(row.probe.at).toLocaleTimeString(), row.probe.tools !== void 0 && ` \xB7 ${t("\u5DE5\u5177\u6570")} ${row.probe.tools}`))), tab === "experiments" && /* @__PURE__ */ import_react4.default.createElement("div", { className: "hub-resource-actions" }, /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Pill, null, /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.StateDot, { state: row.pendingRestart ? "warning" : row.activeEnabled ? "done" : "idle" }), t(row.busy || busy ? "\u5B9E\u9A8C\u6027\u529F\u80FD\u6B63\u5728\u66F4\u65B0" : row.pendingRestart ? "\u91CD\u542F DSH \u540E\u751F\u6548" : row.activeEnabled === null ? "\u8FD0\u884C\u72B6\u6001\u672A\u77E5" : row.activeEnabled ? "\u5DF2\u542F\u7528" : "\u5DF2\u7981\u7528")), row.pendingRestart && /* @__PURE__ */ import_react4.default.createElement("span", null, t(row.enabled ? "\u914D\u7F6E\u5DF2\u542F\u7528" : "\u914D\u7F6E\u5DF2\u7981\u7528")), !row.supported && /* @__PURE__ */ import_react4.default.createElement("span", null, t("\u5F53\u524D\u5BBF\u4E3B\u4E0D\u652F\u6301\u7BA1\u7406\u6B64\u5B9E\u9A8C\u6027\u529F\u80FD")), !row.installed && row.canInstall && /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Button, { size: "sm", disabled: busy || row.busy, onClick: () => {
383
415
  void changeExperiment(row, row.enabled, "install");
384
- } }, t("\u5B89\u88C5 Agent Teams"))), tab !== "experiments" && /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-resource-card-footer" }, /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-resource-actions" }, tab === "plugins" && /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "sm", disabled: busy, icon: /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.IconFolderOpenOutline16, null), onClick: () => run({ id: row.name, kind: "plugin" }, "open-directory") }, t("\u6253\u5F00\u76EE\u5F55"))), tab === "skills" && /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "sm", disabled: !row.directory || busy, onClick: () => {
416
+ } }, t("\u5B89\u88C5 Agent Teams"))), tab !== "experiments" && /* @__PURE__ */ import_react4.default.createElement("div", { className: "hub-resource-card-footer" }, /* @__PURE__ */ import_react4.default.createElement("div", { className: "hub-resource-actions" }, tab === "plugins" && /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, null, /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Button, { size: "sm", disabled: busy, icon: /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.IconFolderOpenOutline16, null), onClick: () => run({ id: row.name, kind: "plugin" }, "open-directory") }, t("\u6253\u5F00\u76EE\u5F55"))), tab === "skills" && /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, null, /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Button, { size: "sm", disabled: !row.directory || busy, onClick: () => {
385
417
  setBusy(true);
386
418
  void action({ id: row.id }, "skill-file").then(setFile).catch(() => {
387
419
  });
388
- } }, t("\u67E5\u770B SKILL.md")), /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "sm", disabled: !row.directory || busy, onClick: () => run({ id: row.id }, "open-directory"), icon: /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.IconFolderOpenOutline16, null) }, t("\u6253\u5F00\u76EE\u5F55"))), tab === "mcps" && /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Tooltip, { label: t("\u72EC\u7ACB\u6D4B\u8BD5\u4F1A\u4E34\u65F6\u8FDE\u63A5\u670D\u52A1\u5E76\u8BFB\u53D6\u5DE5\u5177\u5217\u8868\uFF0C\u4E0D\u6267\u884C\u5DE5\u5177\u3002") }, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "sm", disabled: busy || !row.enabled, onClick: () => run({ id: row.id, revision: row.revision }, "mcp-test") }, t("\u6D4B\u8BD5\u8FDE\u63A5"))), /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "sm", disabled: busy || !row.enabled, onClick: () => run({ action: "mcp-reconnect", id: row.id, revision: row.revision }) }, t("\u91CD\u65B0\u8FDE\u63A5")), /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "sm", disabled: busy, onClick: () => setEditor(row) }, t("\u7F16\u8F91")), /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Tooltip, { label: row.canDelete ? t("\u5220\u9664") : t("\u6765\u81EA\u5171\u4EAB\u914D\u7F6E\u6216\u5BBF\u4E3B\u5305\uFF0C\u8BF7\u5728\u6765\u6E90\u5904\u5220\u9664") }, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { size: "sm", disabled: busy || !row.canDelete, onClick: () => setDeleting(row) }, t("\u5220\u9664"))))), /* @__PURE__ */ import_react.default.createElement("p", { className: "hub-resource-path hub-resource-source", title: row.directory ?? row.source }, t("\u6765\u6E90"), " \xB7 ", row.directory ?? row.source))))), data && !rows.length && /* @__PURE__ */ import_react.default.createElement("p", { className: "hub-resource-empty" }, t("\u672A\u627E\u5230\u5339\u914D\u8D44\u6E90")))), file && /* @__PURE__ */ import_react.default.createElement(SkillPreview, { key: file.path, file, t, close: () => setFile(null) }), /* @__PURE__ */ import_react.default.createElement(ResourceModal, { className: "hub-resource-dialog", open: desktopHelp, title: t("\u684C\u9762\u63D2\u4EF6\u7BA1\u7406"), closeLabel: t("\u5173\u95ED"), onClose: () => setDesktopHelp(false) }, /* @__PURE__ */ import_react.default.createElement("p", null, t("\u5F53\u524D DSH \u4E3B\u7A97\u53E3\u672A\u63D0\u4F9B\u6253\u5F00\u7BA1\u7406\u7A97\u53E3\u7684\u63A5\u53E3\u3002\u8BF7\u4F7F\u7528\u7CFB\u7EDF\u83DC\u5355\u7684 Desktop Plugins\uFF0C\u6216\u6309"), " ", /* @__PURE__ */ import_react.default.createElement("kbd", null, data?.profile.platform === "darwin" ? "\u2318 ," : "Ctrl+,"))), editor && /* @__PURE__ */ import_react.default.createElement(McpEditor, { request, profile: data.profile.directory, row: editor.id ? editor : null, t, busy, close: () => setEditor(null), save: async (body) => {
420
+ } }, t("\u67E5\u770B SKILL.md")), /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Button, { size: "sm", disabled: !row.directory || busy, onClick: () => run({ id: row.id }, "open-directory"), icon: /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.IconFolderOpenOutline16, null) }, t("\u6253\u5F00\u76EE\u5F55"))), tab === "mcps" && /* @__PURE__ */ import_react4.default.createElement(import_react4.default.Fragment, null, /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Tooltip, { label: t("\u72EC\u7ACB\u6D4B\u8BD5\u4F1A\u4E34\u65F6\u8FDE\u63A5\u670D\u52A1\u5E76\u8BFB\u53D6\u5DE5\u5177\u5217\u8868\uFF0C\u4E0D\u6267\u884C\u5DE5\u5177\u3002") }, /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Button, { size: "sm", disabled: busy || !row.enabled, onClick: () => run({ id: row.id, revision: row.revision }, "mcp-test") }, t("\u6D4B\u8BD5\u8FDE\u63A5"))), /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Button, { size: "sm", disabled: busy || !row.enabled, onClick: () => run({ action: "mcp-reconnect", id: row.id, revision: row.revision }) }, t("\u91CD\u65B0\u8FDE\u63A5")), /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Button, { size: "sm", disabled: busy, onClick: () => setEditor(row) }, t("\u7F16\u8F91")), /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Tooltip, { label: row.canDelete ? t("\u5220\u9664") : t("\u6765\u81EA\u5171\u4EAB\u914D\u7F6E\u6216\u5BBF\u4E3B\u5305\uFF0C\u8BF7\u5728\u6765\u6E90\u5904\u5220\u9664") }, /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Button, { size: "sm", disabled: busy || !row.canDelete, onClick: () => setDeleting(row) }, t("\u5220\u9664"))))), /* @__PURE__ */ import_react4.default.createElement("p", { className: "hub-resource-path hub-resource-source", title: row.directory ?? row.source }, t("\u6765\u6E90"), " \xB7 ", row.directory ?? row.source))))), data && !rows.length && /* @__PURE__ */ import_react4.default.createElement("p", { className: "hub-resource-empty" }, t("\u672A\u627E\u5230\u5339\u914D\u8D44\u6E90")))), file && /* @__PURE__ */ import_react4.default.createElement(SkillPreview, { key: file.path, file, t, close: () => setFile(null) }), /* @__PURE__ */ import_react4.default.createElement(ResourceModal, { className: "hub-resource-dialog", open: desktopHelp, title: t("\u684C\u9762\u63D2\u4EF6\u7BA1\u7406"), closeLabel: t("\u5173\u95ED"), onClose: () => setDesktopHelp(false) }, /* @__PURE__ */ import_react4.default.createElement("p", null, t("\u5F53\u524D DSH \u4E3B\u7A97\u53E3\u672A\u63D0\u4F9B\u6253\u5F00\u7BA1\u7406\u7A97\u53E3\u7684\u63A5\u53E3\u3002\u8BF7\u4F7F\u7528\u7CFB\u7EDF\u83DC\u5355\u7684 Desktop Plugins\uFF0C\u6216\u6309"), " ", /* @__PURE__ */ import_react4.default.createElement("kbd", null, data?.profile.platform === "darwin" ? "\u2318 ," : "Ctrl+,"))), editor && /* @__PURE__ */ import_react4.default.createElement(McpEditor, { request, profile: data.profile.directory, row: editor.id ? editor : null, t, busy, close: () => setEditor(null), save: async (body) => {
389
421
  await action(body);
390
422
  setEditor(null);
391
- } }), /* @__PURE__ */ import_react.default.createElement(ResourceModal, { className: "hub-resource-dialog", contentClassName: "hub-resource-dialog-scroll", open: Boolean(deleting), title: t("\u786E\u8BA4\u5220\u9664"), closeLabel: t("\u5173\u95ED"), onClose: () => setDeleting(null), footer: /* @__PURE__ */ import_react.default.createElement("div", { className: "hub-resource-actions" }, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { disabled: busy, onClick: () => setDeleting(null) }, t("\u53D6\u6D88")), /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { variant: "primary", disabled: busy, onClick: () => {
423
+ } }), /* @__PURE__ */ import_react4.default.createElement(ResourceModal, { className: "hub-resource-dialog", contentClassName: "hub-resource-dialog-scroll", open: Boolean(deleting), title: t("\u786E\u8BA4\u5220\u9664"), closeLabel: t("\u5173\u95ED"), onClose: () => setDeleting(null), footer: /* @__PURE__ */ import_react4.default.createElement("div", { className: "hub-resource-actions" }, /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Button, { disabled: busy, onClick: () => setDeleting(null) }, t("\u53D6\u6D88")), /* @__PURE__ */ import_react4.default.createElement(import_dsh_client_ui_primitives4.Button, { variant: "primary", disabled: busy, onClick: () => {
392
424
  void action({ action: "mcp-delete", id: deleting.id, revision: deleting.revision }).then(() => setDeleting(null)).catch(() => {
393
425
  });
394
- } }, t("\u5220\u9664"))) }, /* @__PURE__ */ import_react.default.createElement("p", null, deleting?.name), /* @__PURE__ */ import_react.default.createElement("p", null, t("\u5220\u9664\u4F1A\u65AD\u5F00\u8BE5 MCP\uFF0C\u5E76\u79FB\u9664\u5BF9\u5E94\u7684\u914D\u7F6E\u6761\u76EE\u3002"))));
395
- }
396
- function HubSidebarButton({ wide, usePanelInfo, ctx, panelId, brand }) {
397
- const selected = usePanelInfo((state) => state.activePanelId === panelId);
398
- return /* @__PURE__ */ import_react.default.createElement(import_react.default.Fragment, null, /* @__PURE__ */ import_react.default.createElement("style", null, resources_default), /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Tooltip, { label: brand.navTitle, disabled: wide }, /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.Button, { className: `hub-sidebar-button ${wide ? "is-wide" : "is-rail"}`, "aria-label": brand.navTitle, "aria-pressed": selected, icon: /* @__PURE__ */ import_react.default.createElement(import_dsh_client_ui_primitives.IconCordisPluginOutline14, { size: 18 }), onClick: () => ctx.layout.selectPanel(selected ? null : panelId) }, wide && /* @__PURE__ */ import_react.default.createElement("span", null, brand.navTitle))));
426
+ } }, t("\u5220\u9664"))) }, /* @__PURE__ */ import_react4.default.createElement("p", null, deleting?.name), /* @__PURE__ */ import_react4.default.createElement("p", null, t("\u5220\u9664\u4F1A\u65AD\u5F00\u8BE5 MCP\uFF0C\u5E76\u79FB\u9664\u5BF9\u5E94\u7684\u914D\u7F6E\u6761\u76EE\u3002"))));
399
427
  }
400
428
 
401
429
  // src/client/locale.js
402
430
  var resourceWords = {
431
+ "\u5F53\u524D DSH \u672A\u63D0\u4F9B\u53EF\u7528\u7684 MCP \u914D\u7F6E\u89C4\u5219": "This DSH host does not expose a supported MCP configuration schema.",
432
+ "MCP \u914D\u7F6E\u4E0D\u7B26\u5408\u5F53\u524D DSH \u7684\u89C4\u5219\uFF0C\u8BF7\u6838\u5BF9\u5B57\u6BB5\u7C7B\u578B\u548C\u53D6\u503C": "The MCP configuration does not match this DSH host's schema. Check field types and values.",
433
+ "\u670D\u52A1\u7AEF\u6307\u4EE4\u7684 UTF-8 \u5B57\u8282\u4E0A\u9650\uFF0C\u5305\u542B\u670D\u52A1\u540D\u79F0\u524D\u7F00\uFF1B\u8D85\u51FA\u65F6\u8FDE\u63A5\u5931\u8D25\u3002": "Maximum UTF-8 bytes of server instructions, including the server-name prefix. Exceeding it fails the connection.",
434
+ "\u5355\u6B21\u5DE5\u5177\u8C03\u7528\u6216\u8D44\u6E90\u8BF7\u6C42\u8D85\u65F6\uFF0C\u5355\u4F4D\u6BEB\u79D2\u3002": "Timeout per tool call or resource request, in milliseconds.",
435
+ "\u7531\u5F53\u524D DSH \u63D0\u4F9B\u7684\u914D\u7F6E\u9879\u3002": "Configuration field provided by this DSH host.",
436
+ "\u672A\u63D0\u4F9B\u9ED8\u8BA4\u503C": "No default specified",
403
437
  "\u65E0\u6548\u7684\u5B9E\u9A8C\u6027\u529F\u80FD\u8BF7\u6C42": "Invalid experiment request.",
404
438
  "\u5F53\u524D\u5BBF\u4E3B\u4E0D\u652F\u6301\u7BA1\u7406\u6B64\u5B9E\u9A8C\u6027\u529F\u80FD": "This host does not support managing this experiment.",
405
439
  "\u5B9E\u9A8C\u6027\u529F\u80FD\u72B6\u6001\u5DF2\u53D8\u5316\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u8BD5": "The experiment state has changed. Refresh and try again.",
@@ -411,7 +445,6 @@ var resourceWords = {
411
445
  "\u914D\u7F6E\u5DF2\u542F\u7528": "Configured to enable",
412
446
  "\u914D\u7F6E\u5DF2\u7981\u7528": "Configured to disable",
413
447
  "\u5B9E\u9A8C\u6027\u591A Agent \u534F\u4F5C\u5DE5\u5177\u4E0E\u56E2\u961F\u9762\u677F\u3002Web \u5207\u6362\u540E\u9700\u91CD\u542F DSH\uFF1B\u652F\u6301\u7BA1\u7406\u7684 Desktop \u4F1A\u81EA\u52A8\u91CD\u542F\u540E\u7AEF\uFF0C\u4F1A\u8BDD\u6570\u636E\u4FDD\u7559\u3002": "Experimental multi-agent tools and team panel. Restart DSH after Web changes; supported Desktop hosts restart the backend automatically. Conversations are retained.",
414
- "HTTP \u5FC5\u586B": "Required for HTTP",
415
448
  "HTTP \u8BF7\u6C42\u5934\uFF0C\u53EF\u5F15\u7528\u73AF\u5883\u53D8\u91CF\u3002": "HTTP request headers; environment references are supported.",
416
449
  "Hub profile \u914D\u7F6E\u4E0E\u5F53\u524D\u5B9E\u4F8B\u4E0D\u4E00\u81F4": "Hub's configured profile does not match this instance.",
417
450
  "Hub \u6B63\u5728\u5173\u95ED": "Hub is shutting down.",
@@ -421,7 +454,6 @@ var resourceWords = {
421
454
  "JSON / YAML \u683C\u5F0F\u65E0\u6548\uFF0C\u8BF7\u68C0\u67E5\u7F29\u8FDB\u3001\u5F15\u53F7\u548C\u91CD\u590D\u5B57\u6BB5": "Invalid JSON or YAML. Check indentation, quotes and duplicate fields.",
422
455
  "MCP URL \u5FC5\u987B\u4F7F\u7528 HTTP(S)\uFF0C\u51ED\u636E\u8BF7\u653E\u5165 headers": "The MCP URL must use HTTP(S). Put credentials in headers.",
423
456
  "MCP \u4E3A\u5F53\u524D profile \u7684\u5168\u5C40\u670D\u52A1\uFF1B\u5DE5\u4F5C\u533A\u8303\u56F4\u53EA\u7B5B\u9009 Skill\u3002": "MCP services belong to this profile. The workspace selector filters Skills only.",
424
- "MCP \u540D\u79F0\u53EA\u5141\u8BB8 1\u201332 \u4F4D\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u548C\u8FDE\u5B57\u7B26": "MCP names must contain 1\u201332 letters, digits, underscores or hyphens.",
425
457
  "MCP \u5DF2\u4E0D\u5B58\u5728": "This MCP no longer exists.",
426
458
  "MCP \u914D\u7F6E": "MCP configuration",
427
459
  "MCP \u914D\u7F6E\u5DF2\u53D8\u5316\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u8BD5": "The MCP configuration has changed. Refresh and try again.",
@@ -430,15 +462,10 @@ var resourceWords = {
430
462
  "Skill \u6587\u4EF6\u4E0D\u5728\u6765\u6E90\u76EE\u5F55\u5185": "The Skill file is outside its source directory.",
431
463
  "Skill \u6587\u4EF6\u8FC7\u5927\uFF0C\u8BF7\u6253\u5F00\u76EE\u5F55\u67E5\u770B": "The Skill file is too large to preview. Open its directory to view it.",
432
464
  "Streamable HTTP \u5730\u5740\u3002": "The Streamable HTTP endpoint URL.",
433
- "args \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6570\u7EC4": "args must be an array of strings.",
434
465
  "cwd \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84": "cwd must be an absolute path.",
435
- "env / headers \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u952E\u503C\u5BF9\u8C61": "env and headers must be objects with string values.",
436
- "failOnStartupError \u5FC5\u987B\u662F\u5E03\u5C14\u503C": "failOnStartupError must be a boolean.",
437
- "reconnect \u914D\u7F6E\u65E0\u6548": "Invalid reconnect configuration.",
438
466
  "stdio \u4E3A\u672C\u5730\u8FDB\u7A0B\uFF1Bstreamable-http \u4E3A HTTP \u670D\u52A1\u3002": "stdio runs a local process; streamable-http connects to an HTTP service.",
439
467
  "stdio \u53EF\u6267\u884C\u6587\u4EF6\uFF1BWindows \u53EF\u4F7F\u7528 node.exe \u7684\u5B8C\u6574\u8DEF\u5F84\u3002": "The stdio executable. On Windows, a full path to node.exe is supported.",
440
468
  "stdio \u5DE5\u4F5C\u76EE\u5F55\uFF0C\u7559\u7A7A\u4F7F\u7528\u5BBF\u4E3B\u5DE5\u4F5C\u76EE\u5F55\u3002": "The stdio working directory. Leave empty to use the host's working directory.",
441
- "stdio \u5FC5\u586B": "Required for stdio",
442
469
  "type \u4E0E transport \u4E0D\u4E00\u81F4": "type and transport do not match.",
443
470
  "\u4E00\u6B21\u8FDE\u63A5\u6545\u969C\u7684\u8FDE\u7EED\u91CD\u8BD5\u6B21\u6570\u4E0A\u9650\u3002": "Maximum consecutive retries for one connection failure.",
444
471
  "\u4E0B\u5217\u9ED8\u8BA4\u503C\u7531 DSH \u63D0\u4F9B\uFF1B\u5207\u6362\u683C\u5F0F\u4E0D\u4F1A\u8865\u5165\u8FD9\u4E9B\u5B57\u6BB5\u3002": "DSH supplies these defaults. Switching formats does not insert them.",
@@ -457,7 +484,6 @@ var resourceWords = {
457
484
  "\u52A0\u8F7D\u5931\u8D25": "Activation failed",
458
485
  "\u52A0\u8F7D\u672A\u77E5": "Activation unknown",
459
486
  "\u52A0\u8F7D\u72B6\u6001": "Activation",
460
- "\u5355\u6B21\u5DE5\u5177\u8C03\u7528\u8D85\u65F6\uFF0C\u5355\u4F4D\u6BEB\u79D2\u3002": "Timeout for each tool call, in milliseconds.",
461
487
  "\u539F\u751F DSH \u5B89\u88C5\u5931\u8D25\uFF0C\u8BF7\u786E\u8BA4\u6B64 DSH \u7248\u672C\u63D0\u4F9B Agent Teams \u5305": "Native DSH installation failed. Check whether this DSH version provides Agent Teams packages.",
462
488
  "\u53D6\u6D88": "Cancel",
463
489
  "\u542F\u7528": "Enable",
@@ -470,7 +496,6 @@ var resourceWords = {
470
496
  "\u5B9E\u4F8B\u6216\u529F\u80FD\u5DF2\u53D8\u5316\uFF0C\u8BF7\u5237\u65B0": "The instance or feature has changed. Refresh to continue.",
471
497
  "\u5B9E\u9A8C\u6027\u529F\u80FD": "Experimental",
472
498
  "\u5B9E\u9A8C\u6027\u529F\u80FD\u6B63\u5728\u66F4\u65B0": "An experimental feature is being updated.",
473
- "\u5B9E\u9A8C\u6027\u591A Agent \u534F\u4F5C\u5DE5\u5177\u4E0E\u56E2\u961F\u9762\u677F\u3002\u5207\u6362\u540E\u9700\u91CD\u542F DSH\uFF0C\u4F1A\u8BDD\u6570\u636E\u4FDD\u7559\u3002": "Experimental multi-agent tools and team panel. Changes require restarting DSH; conversations are preserved.",
474
499
  "\u5BBF\u4E3B\u65E0\u6CD5\u6253\u5F00\u76EE\u5F55\uFF0C\u8BF7\u6839\u636E\u6765\u6E90\u8DEF\u5F84\u624B\u52A8\u6253\u5F00": "The host could not open the directory. Open it manually using the source path.",
475
500
  "\u5BBF\u4E3B\u672A\u516C\u5F00\u6301\u7EED\u8FDE\u63A5\u72B6\u6001\uFF1B\u5DF2\u52A0\u8F7D\u4E0D\u4EE3\u8868\u8FDE\u63A5\u6210\u529F\u3002": "The host does not expose continuous connection status. Activation does not imply a successful connection.",
476
501
  "\u5DE5\u5177\u547D\u540D\u7A7A\u95F4\uFF0C1\u201332 \u4F4D\u5B57\u6BCD\u3001\u6570\u5B57\u3001\u4E0B\u5212\u7EBF\u6216\u8FDE\u5B57\u7B26\u3002": "Tool namespace: 1\u201332 letters, digits, underscores or hyphens.",
@@ -498,6 +523,7 @@ var resourceWords = {
498
523
  "\u65E0\u6548\u7684\u5F00\u5173\u503C": "Invalid toggle value.",
499
524
  "\u65E0\u6CD5\u786E\u8BA4\u5F53\u524D\u5B9E\u4F8B\u7684 profile\uFF0C\u5DF2\u505C\u6B62\u7BA1\u7406\u64CD\u4F5C": "Cannot identify this instance's profile. Management operations are disabled.",
500
525
  "\u663E\u5F0F\u4F20\u7ED9\u670D\u52A1\u7684\u73AF\u5883\u53D8\u91CF\uFF0C\u53EF\u7528 !!js process.env.NAME \u5F15\u7528\u3002": "Environment variables passed to the server. Use !!js process.env.NAME for references.",
526
+ "\u670D\u52A1\u672A\u63D0\u4F9B\u63CF\u8FF0\u3002": "The server did not provide a description.",
501
527
  "\u6682\u65E0\u63CF\u8FF0": "No description",
502
528
  "\u670D\u52A1\u5C1A\u672A\u63D0\u4F9B\u63CF\u8FF0\uFF0C\u53EF\u6D4B\u8BD5\u8FDE\u63A5\u8BFB\u53D6\u3002": "No server description available. Test the connection to retrieve it.",
503
529
  "\u672A\u52A0\u8F7D": "Not loaded",
@@ -534,11 +560,9 @@ var resourceWords = {
534
560
  "\u8BF7\u5148\u542F\u7528 MCP": "Enable the MCP first.",
535
561
  "\u8BF7\u5148\u5B89\u88C5 Agent Teams": "Install Agent Teams first.",
536
562
  "\u8BF7\u63D0\u4F9B\u4E00\u4E2A MCP \u914D\u7F6E\u5BF9\u8C61": "Provide one MCP configuration object.",
537
- "\u8BF7\u901A\u8FC7 Desktop \u539F\u751F\u63D2\u4EF6\u7BA1\u7406\u7A97\u53E3\u5207\u6362 Agent Teams": "Use the native Desktop Plugins window to toggle Agent Teams.",
538
563
  "\u8D44\u6E90\u5C1A\u672A\u5B8C\u6574\u52A0\u8F7D\uFF0C\u8BF7\u5237\u65B0\u91CD\u8BD5": "Resource discovery is incomplete. Refresh to retry.",
539
564
  "\u8D44\u6E90\u7BA1\u7406": "Resource management",
540
565
  "\u8D44\u6E90\u8303\u56F4": "Resource scope",
541
- "\u8D85\u65F6\u65F6\u95F4\u5FC5\u987B\u5728 1000\u2013300000 \u6BEB\u79D2\u4E4B\u95F4": "The timeout must be between 1000 and 300000 milliseconds.",
542
566
  "\u8FD4\u56DE\u4F1A\u8BDD": "Back to conversation",
543
567
  "\u8FD9\u4E2A\u8D44\u6E90\u6CA1\u6709\u672C\u5730\u76EE\u5F55": "This resource has no local directory.",
544
568
  "\u8FDE\u63A5\u4E22\u5931\u540E\u81EA\u52A8\u91CD\u8FDE\u3002": "Reconnect automatically after losing the connection.",
@@ -652,7 +676,8 @@ function apply(ctx) {
652
676
  const request = connectionRequest(ctx.connection, MARKET_ID);
653
677
  const experiments = experimentClient(request, window.dshDesktop, () => window.location.reload());
654
678
  const panelId = "plugin-hub-" + MARKET_ID;
655
- ctx.slots.inject("main", () => ctx.slots.register({ name: "main", key: panelId, locale: NS }, () => /* @__PURE__ */ import_react2.default.createElement(ResourceHub, { ctx, request, experiments, t, brand: BRAND, panelId })));
656
- ctx.slots.inject("sidebar.footer.action", () => ctx.slots.register({ name: "sidebar.footer.action", id: panelId, order: 39, locale: NS }, (props) => /* @__PURE__ */ import_react2.default.createElement(HubSidebarButton, { ...props, ctx, panelId, brand: BRAND })));
679
+ ctx.slots.inject("main", () => ctx.slots.register({ name: "main", key: panelId, locale: NS }, () => /* @__PURE__ */ import_react5.default.createElement(ResourceHub, { ctx, request, experiments, t, brand: BRAND, panelId })));
680
+ ctx.slots.inject("conversation.session.header.utilities", () => ctx.slots.register({ name: "conversation.session.header.utilities", id: panelId, order: 100, locale: NS }, () => /* @__PURE__ */ import_react5.default.createElement(HubHeaderEntry, { ctx, panelId, brand: BRAND })));
681
+ ctx.slots.inject("shell.overlay", () => ctx.slots.register({ name: "shell.overlay", id: panelId, locale: NS }, (props) => /* @__PURE__ */ import_react5.default.createElement(HubEmptyEntry, { ...props, ctx, panelId, brand: BRAND })));
657
682
  }
658
683
  return module.exports; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zaimokuza/dsh-plugin-hub",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "private": false,
5
5
  "description": "Native DSH resource hub: Skills, MCP, third-party plugins and experiments",
6
6
  "type": "module",
@@ -22,7 +22,7 @@
22
22
  "@deepseek-ai/dsh-client-locale",
23
23
  "@deepseek-ai/dsh-client-connection",
24
24
  "@deepseek-ai/dsh-client-ui-layout",
25
- "@deepseek-ai/dsh-client-ui-sidebar"
25
+ "@deepseek-ai/dsh-client-ui-conversation"
26
26
  ],
27
27
  "platform": "web"
28
28
  }
@@ -36,10 +36,14 @@
36
36
  "verify:native": "node scripts/verify-native.mjs"
37
37
  },
38
38
  "dependencies": {
39
- "@modelcontextprotocol/sdk": "1.30.0",
39
+ "@modelcontextprotocol/client": "2.0.0",
40
40
  "yaml": "2.9.1"
41
41
  },
42
42
  "devDependencies": {
43
+ "@deepseek-ai/schemastery": "3.18.2",
44
+ "@modelcontextprotocol/node": "2.0.0",
45
+ "@modelcontextprotocol/sdk": "1.30.0",
46
+ "@modelcontextprotocol/server": "2.0.0",
43
47
  "esbuild": "0.28.1",
44
48
  "react": "18.3.1"
45
49
  },
@@ -70,7 +74,8 @@
70
74
  "hostCompatibility": "capability-based",
71
75
  "testedDshVersions": [
72
76
  "0.1.5-rc.1",
73
- "0.1.5-rc.2"
77
+ "0.1.5-rc.2",
78
+ "0.1.6-alpha.1"
74
79
  ]
75
80
  }
76
81
  }
package/src/index.js CHANGED
@@ -24,6 +24,7 @@ export async function apply(ctx, options = {}) {
24
24
  if (path === 'resources') return resources.snapshot(data.workspaceId, data.presetId);
25
25
  if (path === 'resource-mutate') return resources.mutate(data);
26
26
  if (path === 'mcp-config') return resources.configuration(data);
27
+ if (path === 'mcp-schema') return resources.configurationSchema(data);
27
28
  if (path === 'mcp-format') return resources.formatConfiguration(data);
28
29
  if (path === 'mcp-test') return resources.test(data, signal);
29
30
  if (path === 'open-directory') return resources.open(data);
@@ -34,7 +35,7 @@ export async function apply(ctx, options = {}) {
34
35
  };
35
36
  // The shared RPC interceptor belongs to DSH's Gateway. Exact Fetch routes
36
37
  // compose ahead of it on both the HTTP carrier and Desktop's worker carrier.
37
- for (const path of ['resources', 'resource-mutate', 'mcp-config', 'mcp-format', 'mcp-test', 'open-directory', 'skill-file', 'experiments', 'experiment-mutate']) {
38
+ for (const path of ['resources', 'resource-mutate', 'mcp-config', 'mcp-schema', 'mcp-format', 'mcp-test', 'open-directory', 'skill-file', 'experiments', 'experiment-mutate']) {
38
39
  ctx.effect(() => ctx.connection.fetch.register({ path: `/api/${identity.service}/${path}`, methods: ['POST'], requestBody: 'buffered', async fetch(request) {
39
40
  let envelope;
40
41
  try { envelope = await request.json(); } catch { return Response.json({ error: 'Invalid JSON' }, { status: 400 }); }
@@ -0,0 +1,76 @@
1
+ import { isAbsolute } from 'node:path';
2
+
3
+ export const MCP_MODULE = '@deepseek-ai/dsh-mcp-client';
4
+ const object = value => value && typeof value === 'object' && !Array.isArray(value);
5
+
6
+ function variants(schema) {
7
+ const nodes = schema?.type === 'union' ? schema.list : [schema];
8
+ if (!nodes?.length || nodes.some(node => node?.type !== 'object' || !node.dict?.transport)) {
9
+ throw new Error('当前 DSH 未提供可用的 MCP 配置规则');
10
+ }
11
+ return nodes;
12
+ }
13
+
14
+ // Schemastery preserves unknown keys. Reject typos using the host's field tree,
15
+ // without maintaining a second list of supported MCP options in Hub.
16
+ function knownFields(value, schema) {
17
+ if (!object(value) || schema.type !== 'object') return;
18
+ for (const [key, child] of Object.entries(value)) {
19
+ if (!Object.hasOwn(schema.dict, key)) throw new Error('配置包含宿主不支持的字段,请核对 MCP 配置');
20
+ knownFields(child, schema.dict[key]);
21
+ }
22
+ }
23
+
24
+ /** The host owns types/defaults; Hub owns its editor's filesystem/URL boundary. */
25
+ export function validateNativeMcp(Config, input) {
26
+ const branch = variants(Config).find(node => node.dict.transport.value === input?.transport);
27
+ if (!branch) throw new Error('不支持的 MCP 协议');
28
+ knownFields(input, branch);
29
+ let config;
30
+ try { config = Config(structuredClone(input)); }
31
+ catch { throw new Error('MCP 配置不符合当前 DSH 的规则,请核对字段类型和取值'); }
32
+ if (config.transport === 'stdio') {
33
+ if (!config.command?.trim() || config.command.includes('\0')) throw new Error('需要有效的可执行命令');
34
+ if (config.cwd && !isAbsolute(config.cwd)) throw new Error('cwd 必须是绝对路径');
35
+ } else if (config.transport === 'streamable-http') {
36
+ let url; try { url = new URL(config.url); } catch { throw new Error('需要有效的 MCP URL'); }
37
+ if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) throw new Error('MCP URL 必须使用 HTTP(S),凭据请放入 headers');
38
+ }
39
+ if (config.reconnect?.initialDelayMs > config.reconnect?.maxDelayMs) throw new Error('重连初始间隔不能超过最大间隔');
40
+ return config;
41
+ }
42
+
43
+ /** Only schema metadata crosses RPC; never resolved configuration or env values. */
44
+ export function mcpSchemaFields(Config) {
45
+ const fields = new Map();
46
+ function visit(schema, transport, prefix = '') {
47
+ for (const [name, node] of Object.entries(schema.dict)) {
48
+ const key = prefix + name;
49
+ if (node.type === 'object') { visit(node, transport, key + '.'); continue; }
50
+ const meta = node.meta ?? {};
51
+ const field = { key, type: node.type, required: Boolean(meta.required) || node.type === 'const',
52
+ ...(Object.hasOwn(meta, 'default') ? { default: meta.default } : {}), transports: [transport] };
53
+ const previous = fields.get(key);
54
+ // Keep distinct rows if transports ever acquire different defaults/types.
55
+ const id = previous && JSON.stringify({ ...previous, transports: [] }) !== JSON.stringify({ ...field, transports: [] }) ? `${key}:${transport}` : key;
56
+ if (fields.has(id)) fields.get(id).transports.push(transport);
57
+ else fields.set(id, field);
58
+ }
59
+ }
60
+ for (const branch of variants(Config)) visit(branch, branch.dict.transport.value);
61
+ return [...fields.values()];
62
+ }
63
+
64
+ export class NativeMcp {
65
+ constructor(loader) { this.loader = loader; }
66
+ async module() {
67
+ this.pending ??= this.loader.import(MCP_MODULE).then(module => {
68
+ if (typeof module.Config !== 'function') throw new Error('当前 DSH 未提供可用的 MCP 配置规则');
69
+ variants(module.Config);
70
+ return module;
71
+ }).catch(error => { this.pending = undefined; throw error; });
72
+ return this.pending;
73
+ }
74
+ async validate(input) { return validateNativeMcp((await this.module()).Config, input); }
75
+ async fields() { return mcpSchemaFields((await this.module()).Config); }
76
+ }
@@ -0,0 +1,53 @@
1
+ import { Client, StreamableHTTPClientTransport } from '@modelcontextprotocol/client';
2
+ import { StdioClientTransport } from '@modelcontextprotocol/client/stdio';
3
+
4
+ const safeIcon = value => { try { const url = new URL(value); return url.protocol === 'https:' && !url.username && !url.password && !url.search ? url.href : null; } catch { return null; } };
5
+
6
+ /** A bounded, independent observation, never the host's live connection state. */
7
+ export async function probeMcp(config, { signal, timeoutMs = 15000 } = {}) {
8
+ const abort = AbortSignal.any([...(signal ? [signal] : []), AbortSignal.timeout(timeoutMs)]);
9
+ const started = Date.now();
10
+ const client = new Client({ name: 'dsh-plugin-hub-probe', version: '1.0.0' }, {
11
+ capabilities: {}, versionNegotiation: { mode: 'auto' },
12
+ });
13
+ let transport;
14
+ let onAbort;
15
+ try {
16
+ abort.throwIfAborted();
17
+ transport = config.transport === 'stdio' ? new StdioClientTransport({ command: config.command, args: config.args,
18
+ env: { ...Object.fromEntries(['PATH', 'HOME', 'USERPROFILE', 'SystemRoot', 'TMPDIR', 'TEMP', 'LANG'].filter(k => process.env[k]).map(k => [k, process.env[k]])), ...config.env },
19
+ cwd: config.cwd || undefined, stderr: 'ignore',
20
+ }) : new StreamableHTTPClientTransport(new URL(config.url), { requestInit: { headers: config.headers } });
21
+ const cancellation = new Promise((_, reject) => {
22
+ onAbort = () => { reject(abort.reason); };
23
+ abort.addEventListener('abort', onAbort, { once: true });
24
+ });
25
+ const result = await Promise.race([(async () => {
26
+ await client.connect(transport, { signal: abort, timeout: timeoutMs });
27
+ abort.throwIfAborted();
28
+ // Match the host's instruction budget when its schema supplies one. Older
29
+ // hosts omit this option; the independent probe must not invent a limit.
30
+ const instructions = client.getInstructions();
31
+ if (instructions && config.maxInstructionBytes !== undefined &&
32
+ Buffer.byteLength(`### MCP server: ${config.serverName}\n\n${instructions}`) > config.maxInstructionBytes) {
33
+ throw new Error('MCP server instructions exceed the host limit');
34
+ }
35
+ // SDK owns pagination and supports resource-only servers with zero tools.
36
+ const { tools } = client.getServerCapabilities()?.tools ? await client.listTools(undefined, { signal: abort, timeout: timeoutMs }) : { tools: [] };
37
+ const info = client.getServerVersion();
38
+ const capabilities = client.getServerCapabilities();
39
+ return { status: 'success', tools: tools.length, version: info?.version ?? null,
40
+ icon: safeIcon(info?.icons?.[0]?.src), description: String(info?.description ?? '').slice(0, 8000),
41
+ capabilities: { tools: Boolean(capabilities?.tools), resources: Boolean(capabilities?.resources), prompts: Boolean(capabilities?.prompts) },
42
+ };
43
+ })(), cancellation]);
44
+ return { ...result, durationMs: Date.now() - started, at: new Date().toISOString() };
45
+ } catch {
46
+ return { status: 'failed', durationMs: Date.now() - started, at: new Date().toISOString() };
47
+ } finally {
48
+ if (onAbort) abort.removeEventListener('abort', onAbort);
49
+ // During negotiation the transport (and its probe sibling) may not yet be
50
+ // attached to Client. Both owners must be closed on cancellation/failure.
51
+ await Promise.allSettled([client.close(), transport?.close()]);
52
+ }
53
+ }
package/src/resources.js CHANGED
@@ -2,15 +2,13 @@ import { readFile, mkdir, realpath } from 'node:fs/promises';
2
2
  import { join, relative, isAbsolute } from 'node:path';
3
3
  import { createHash, randomUUID } from 'node:crypto';
4
4
  import { spawn } from 'node:child_process';
5
- import { Client } from '@modelcontextprotocol/sdk/client/index.js';
6
- import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
7
- import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
5
+ import { NativeMcp, MCP_MODULE as MCP } from './mcp-native.js';
6
+ import { probeMcp } from './mcp-probe.js';
8
7
 
9
8
  import { parseMcpDocument, formatMcpDocument, parseConfigText, resolveEnvironment } from './mcp-config.js';
10
9
 
11
10
  import { findMcpDefinition, deleteMcpDefinition, atomicWrite } from './profile-files.js';
12
11
 
13
- const MCP = '@deepseek-ai/dsh-mcp-client';
14
12
  const fingerprint = value => createHash('sha256').update(JSON.stringify(value)).digest('hex');
15
13
  const inside = (root, path) => { const rel = relative(root, path); return !rel.startsWith('..') && !isAbsolute(rel); };
16
14
  const record = value => value && typeof value === 'object' && !Array.isArray(value);
@@ -24,47 +22,11 @@ export function describeMcp(config = {}) {
24
22
  };
25
23
  }
26
24
 
27
- export function validateMcp(input) {
28
- if (!record(input) || !/^[A-Za-z0-9_-]{1,32}$/.test(input.serverName)) throw new Error('MCP 名称只允许 1–32 位字母、数字、下划线和连字符');
29
- const common = ['serverName', 'transport', 'toolCallTimeoutMs', 'failOnStartupError', 'reconnect'];
30
- const allowed = [...common, ...(input.transport === 'stdio' ? ['command', 'args', 'env', 'cwd'] : ['url', 'headers'])];
31
- if (Object.keys(input).some(key => !allowed.includes(key))) throw new Error('配置包含宿主不支持的字段,请核对 MCP 配置');
32
- const config = { serverName: input.serverName, transport: input.transport };
33
- if (input.transport === 'stdio') {
34
- if (typeof input.command !== 'string' || !input.command.trim() || input.command.includes('\0')) throw new Error('需要有效的可执行命令');
35
- config.command = input.command;
36
- config.args = input.args ?? [];
37
- if (!Array.isArray(config.args) || config.args.some(x => typeof x !== 'string')) throw new Error('args 必须是字符串数组');
38
- config.env = input.env ?? {};
39
- config.cwd = input.cwd ?? '';
40
- if (typeof config.cwd !== 'string' || (config.cwd && !isAbsolute(config.cwd))) throw new Error('cwd 必须是绝对路径');
41
- } else if (input.transport === 'streamable-http') {
42
- let url; try { url = new URL(input.url); } catch { throw new Error('需要有效的 MCP URL'); }
43
- if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password) throw new Error('MCP URL 必须使用 HTTP(S),凭据请放入 headers');
44
- config.url = url.href; config.headers = input.headers ?? {};
45
- } else throw new Error('不支持的 MCP 协议');
46
- for (const map of [config.env, config.headers].filter(Boolean)) {
47
- if (!record(map) || Object.values(map).some(x => typeof x !== 'string')) throw new Error('env / headers 必须是字符串键值对象');
48
- }
49
- config.toolCallTimeoutMs = input.toolCallTimeoutMs ?? 60000;
50
- if (!Number.isInteger(config.toolCallTimeoutMs) || config.toolCallTimeoutMs < 1000 || config.toolCallTimeoutMs > 300000) throw new Error('超时时间必须在 1000–300000 毫秒之间');
51
- config.failOnStartupError = input.failOnStartupError ?? false;
52
- if (typeof config.failOnStartupError !== 'boolean') throw new Error('failOnStartupError 必须是布尔值');
53
- if (input.reconnect !== undefined) {
54
- if (!record(input.reconnect) || Object.keys(input.reconnect).some(key => !['enabled', 'initialDelayMs', 'maxDelayMs', 'maxAttempts'].includes(key))) throw new Error('reconnect 配置无效');
55
- config.reconnect = { ...input.reconnect };
56
- for (const [key, value] of Object.entries(config.reconnect)) {
57
- if (key === 'enabled' ? typeof value !== 'boolean' : !Number.isSafeInteger(value) || value < 1 || (key !== 'maxAttempts' && value > 2147483647)) throw new Error('reconnect 配置无效');
58
- }
59
- if (config.reconnect.initialDelayMs && config.reconnect.maxDelayMs && config.reconnect.initialDelayMs > config.reconnect.maxDelayMs) throw new Error('重连初始间隔不能超过最大间隔');
60
- }
61
- return config;
62
- }
63
-
64
25
  /** Native resource adapter. Its durable state belongs exclusively to the boot profile. */
65
26
  export class NativeResources {
66
27
  constructor(ctx, environment, directory, inventory) {
67
28
  Object.assign(this, { ctx, environment, directory, inventory });
29
+ this.mcp = new NativeMcp(ctx.loader);
68
30
  this.file = join(directory, 'resources.json');
69
31
  this.state = { version: 1, skills: [], mcps: [], overrides: {} };
70
32
  this.tail = Promise.resolve(); this.fibers = new Map(); this.applied = new WeakMap(); this.probes = new Map(); this.loadErrors = new Map();
@@ -150,8 +112,8 @@ export class NativeResources {
150
112
  for (const row of this.state.mcps) {
151
113
  if (row.enabled && !this.fibers.has(row.id)) {
152
114
  try {
153
- const module = await this.ctx.loader.import(MCP);
154
- this.fibers.set(row.id, this.ctx.plugin(module, validateMcp(resolveEnvironment(row.config))));
115
+ const module = await this.mcp.module();
116
+ this.fibers.set(row.id, this.ctx.plugin(module, await this.mcp.validate(resolveEnvironment(row.config))));
155
117
  this.loadErrors.delete(row.id);
156
118
  } catch (error) { this.loadErrors.set(row.id, error.message); }
157
119
  }
@@ -218,6 +180,10 @@ export class NativeResources {
218
180
  return { text: formatMcpDocument(config, data.format ?? 'json') };
219
181
  });
220
182
  }
183
+ async configurationSchema(data) {
184
+ if (data.profile !== this.environment.profileDir) throw new Error('实例 profile 已变化,请刷新');
185
+ return { fields: await this.mcp.fields() };
186
+ }
221
187
  formatConfiguration(data) {
222
188
  if (data.profile !== this.environment.profileDir) throw new Error('实例 profile 已变化,请刷新');
223
189
  return { text: formatMcpDocument(parseConfigText(data.text), data.format) };
@@ -243,7 +209,7 @@ export class NativeResources {
243
209
  const previous = next.mcps.find(row => row.id === data.id);
244
210
  const entry = this.entries().find(entry => this.entryId(entry) === data.id);
245
211
  const raw = data.text !== undefined ? parseMcpDocument(data.text) : { ...(previous?.config ?? entry?.options.config ?? {}), ...data.config };
246
- validateMcp(resolveEnvironment(raw));
212
+ await this.mcp.validate(resolveEnvironment(raw));
247
213
  const config = raw;
248
214
  if (snapshot.mcps.some(row => row.name === config.serverName && row.id !== data.id)) throw new Error('当前 profile 已有同名 MCP');
249
215
  if (data.action === 'mcp-add') next.mcps.push({ id: `hub-${randomUUID()}`, config, enabled: true });
@@ -300,24 +266,8 @@ export class NativeResources {
300
266
  const raw = row?.config ?? entry?.options.config;
301
267
  const config = raw && resolveEnvironment(raw);
302
268
  if (!config) throw new Error('当前 MCP 没有可用的已解析配置,请先启用');
303
- const client = new Client({ name: 'dsh-plugin-hub-probe', version: '1.0.0' });
304
- const abort = AbortSignal.any([this.lifecycle.signal, signal ?? new AbortController().signal, AbortSignal.timeout(15000)]);
305
- const transport = config.transport === 'stdio' ? new StdioClientTransport({ command: config.command, args: config.args ?? [],
306
- env: { ...Object.fromEntries(['PATH', 'HOME', 'USERPROFILE', 'SystemRoot', 'TMPDIR', 'TEMP', 'LANG'].filter(k => process.env[k]).map(k => [k, process.env[k]])), ...config.env },
307
- cwd: config.cwd || undefined, stderr: 'ignore',
308
- }) : new StreamableHTTPClientTransport(new URL(config.url), { requestInit: { headers: config.headers } });
309
- const started = Date.now();
310
- let onAbort;
311
- const cancellation = new Promise((_, reject) => {
312
- onAbort = () => { void transport.close(); reject(abort.reason); };
313
- abort.addEventListener('abort', onAbort, { once: true });
314
- });
315
- try {
316
- if (abort.aborted) throw abort.reason;
317
- const result = await Promise.race([(async () => { await client.connect(transport); return client.getServerCapabilities()?.tools ? client.listTools({}, { signal: abort, timeout: 15000 }) : { tools: [] }; })(), cancellation]);
318
- this.probes.set(data.id, { status: 'success', tools: result.tools.length, version: client.getServerVersion()?.version ?? null, icon: safeIcon(client.getServerVersion()?.icons?.[0]?.src), description: String(client.getServerVersion()?.description ?? client.getInstructions() ?? '').slice(0, 8000), durationMs: Date.now() - started, at: new Date().toISOString() });
319
- } catch { this.probes.set(data.id, { status: 'failed', at: new Date().toISOString(), durationMs: Date.now() - started }); }
320
- finally { abort.removeEventListener('abort', onAbort); await client.close().catch(() => {}); }
269
+ const validated = await this.mcp.validate(config);
270
+ this.probes.set(data.id, await probeMcp(validated, { signal: AbortSignal.any([this.lifecycle.signal, ...(signal ? [signal] : [])]) }));
321
271
  return this.snapshot(data.workspaceId, data.presetId);
322
272
  }
323
273
  async skillFile(data) {