@civilization/deepseek-harness-skill-mcp 0.1.5 → 0.1.7
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 +15 -5
- package/lib/client.js +175 -63
- package/package.json +3 -2
- package/src/client/controller.js +9 -0
- package/src/client/index.jsx +50 -20
- package/src/client/locales.js +12 -8
- package/src/client/styles.css +2 -0
- package/src/index.js +1 -1
- package/src/mcp-sse.js +122 -0
- package/src/store.js +12 -3
- package/src/web.js +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
为 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) Web 界面提供 Skill 路径和 MCP 服务器管理。
|
|
4
4
|
|
|
5
|
-
安装后,“设置”中会出现 **Skill 管理** 和 **MCP 管理** 两个页面,同时向 Agent 注册 `extensions_*` 管理工具。插件使用 Harness 自带的 Skill filesystem provider 与 MCP client
|
|
5
|
+
安装后,“设置”中会出现 **Skill 管理** 和 **MCP 管理** 两个页面,同时向 Agent 注册 `extensions_*` 管理工具。插件使用 Harness 自带的 Skill filesystem provider 与 MCP client,并为旧版 HTTP+SSE 服务提供兼容客户端;配置写入当前 `web` profile 的 `cordis.patch.yml`。
|
|
6
6
|
|
|
7
7
|
## 通过 dsh 安装
|
|
8
8
|
|
|
@@ -71,10 +71,11 @@ Skill 页面支持:
|
|
|
71
71
|
|
|
72
72
|
## MCP 管理
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
支持三种 MCP 配置类型:
|
|
75
75
|
|
|
76
76
|
- `stdio`:执行本地命令并通过标准输入输出通信
|
|
77
|
-
- `
|
|
77
|
+
- `streamable-http`:连接推荐的 Streamable HTTP MCP 地址
|
|
78
|
+
- `sse`:兼容使用独立 `/sse` 与 `/messages` 端点的旧版 MCP 服务
|
|
78
79
|
|
|
79
80
|
stdio 示例:
|
|
80
81
|
|
|
@@ -96,13 +97,22 @@ HTTP 示例:
|
|
|
96
97
|
}
|
|
97
98
|
```
|
|
98
99
|
|
|
100
|
+
旧版 SSE 示例:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"transport": "sse",
|
|
105
|
+
"url": "http://127.0.0.1:9000/sse"
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
99
109
|
服务器卡片使用状态灯显示禁用、等待发现工具或工具可用,并可展开查看实际注册的工具列表。MCP 配置可以编辑或删除,编辑时保留服务器 id 与启用状态。新服务器默认禁用;启用 stdio 服务器会在 Agent 沙箱之外执行所配置的程序。
|
|
100
110
|
|
|
101
111
|
MCP 工具是提供给模型的工具 schema,不是用户 `/` 命令,因此不会出现在 Skill 斜杠菜单中;它们的动态状态以 MCP 页面中的“工具列表”和 `extensions_inspect` 为准。
|
|
102
112
|
|
|
103
113
|
当前版本不接受 `env`、认证 header、带凭据或 token 查询参数的 URL。请勿把密钥放进工具参数、MCP 地址或提交到仓库。
|
|
104
114
|
|
|
105
|
-
MCP 对端离线、命令启动失败或首次工具发现失败不会阻止 DSH
|
|
115
|
+
MCP 对端离线、命令启动失败或首次工具发现失败不会阻止 DSH 启动。stdio 与 Streamable HTTP 使用 Harness MCP client 的后台重连策略;旧版 SSE 当前依赖连接自身,在断线后可通过刷新配置重新加载。设置页在连接恢复并发现工具后更新状态。插件加载时也会把旧版本写入的严格启动配置迁移为非致命启动策略。
|
|
106
116
|
|
|
107
117
|
## Agent 工具
|
|
108
118
|
|
|
@@ -110,7 +120,7 @@ MCP 对端离线、命令启动失败或首次工具发现失败不会阻止 DSH
|
|
|
110
120
|
|---|---|
|
|
111
121
|
| `extensions_list` | 查看受管 Skill 路径与 MCP 配置 |
|
|
112
122
|
| `extensions_add_skill` | 添加 Skill 路径;Agent 调用时显式指定内部 id |
|
|
113
|
-
| `extensions_add_mcp` | 添加 stdio
|
|
123
|
+
| `extensions_add_mcp` | 添加 stdio、Streamable HTTP 或旧版 SSE MCP 配置 |
|
|
114
124
|
| `extensions_set_enabled` | 启用或禁用受管配置 |
|
|
115
125
|
| `extensions_update_skill` | 修改 Skill 来源路径 |
|
|
116
126
|
| `extensions_update_mcp` | 替换 MCP 连接配置并保留 id 与启用状态 |
|
package/lib/client.js
CHANGED
|
@@ -30,6 +30,17 @@ module.exports = __toCommonJS(index_exports);
|
|
|
30
30
|
var import_react = require("react");
|
|
31
31
|
|
|
32
32
|
// src/client/controller.js
|
|
33
|
+
var skillCatalogMutations = /* @__PURE__ */ new Set([
|
|
34
|
+
"add-skill",
|
|
35
|
+
"update-skill-source",
|
|
36
|
+
"create-skill",
|
|
37
|
+
"update-skill",
|
|
38
|
+
"delete-skill",
|
|
39
|
+
"set-skill-invocation"
|
|
40
|
+
]);
|
|
41
|
+
function refreshesSlashCatalog(endpoint, options = {}) {
|
|
42
|
+
return options.refreshSlashCatalog === true || skillCatalogMutations.has(endpoint);
|
|
43
|
+
}
|
|
33
44
|
var ExtensionsController = class {
|
|
34
45
|
state = { extensions: [], sources: [], skills: [], revision: "", loading: false, saving: false, error: "", saved: false };
|
|
35
46
|
listeners = /* @__PURE__ */ new Set();
|
|
@@ -99,7 +110,8 @@ var zh = {
|
|
|
99
110
|
directory: "Skill \u8DEF\u5F84",
|
|
100
111
|
transport: "\u914D\u7F6E\u7C7B\u578B",
|
|
101
112
|
stdio: "stdio",
|
|
102
|
-
http: "HTTP",
|
|
113
|
+
http: "Streamable HTTP",
|
|
114
|
+
sse: "SSE\uFF08\u65E7\u7248\u517C\u5BB9\uFF09",
|
|
103
115
|
command: "\u547D\u4EE4",
|
|
104
116
|
args: "\u53C2\u6570\uFF08\u6BCF\u884C\u4E00\u4E2A\uFF09",
|
|
105
117
|
cwd: "\u5DE5\u4F5C\u76EE\u5F55\uFF08\u53EF\u9009\uFF09",
|
|
@@ -110,10 +122,16 @@ var zh = {
|
|
|
110
122
|
create: "\u521B\u5EFA",
|
|
111
123
|
edit: "\u7F16\u8F91",
|
|
112
124
|
delete: "\u5220\u9664",
|
|
125
|
+
confirmDelete: "\u786E\u8BA4\u5220\u9664",
|
|
126
|
+
deleting: "\u5220\u9664\u4E2D\u2026",
|
|
113
127
|
saving: "\u4FDD\u5B58\u4E2D\u2026",
|
|
114
128
|
loading: "\u52A0\u8F7D\u4E2D\u2026",
|
|
115
|
-
|
|
116
|
-
|
|
129
|
+
deleteExtensionTitle: "\u79FB\u9664 Skill \u6765\u6E90",
|
|
130
|
+
deleteMcpTitle: "\u5220\u9664 MCP \u670D\u52A1\u5668",
|
|
131
|
+
deleteSkillTitle: "\u6C38\u4E45\u5220\u9664 Skill",
|
|
132
|
+
deleteExtensionConfirm: "\u5C06\u4ECE\u5F53\u524D\u914D\u7F6E\u4E2D\u79FB\u9664\u201C{name}\u201D\uFF0C\u76EE\u5F55\u4E2D\u7684 Skill \u6587\u4EF6\u4F1A\u4FDD\u7559\u3002",
|
|
133
|
+
deleteMcpConfirm: "\u5C06\u5220\u9664 MCP \u670D\u52A1\u5668\u201C{name}\u201D\u53CA\u5176\u8FDE\u63A5\u914D\u7F6E\u3002",
|
|
134
|
+
deleteSkillConfirm: "\u5C06\u6C38\u4E45\u5220\u9664\u201C{name}\u201D\u53CA\u5176\u76EE\u5F55\u5185\u7684\u8D44\u6E90\u6587\u4EF6\u3002\u6B64\u64CD\u4F5C\u65E0\u6CD5\u64A4\u9500\u3002",
|
|
117
135
|
enabled: "\u914D\u7F6E\u5DF2\u542F\u7528",
|
|
118
136
|
disabled: "\u5DF2\u7981\u7528",
|
|
119
137
|
enable: "\u542F\u7528",
|
|
@@ -122,7 +140,7 @@ var zh = {
|
|
|
122
140
|
failed: "\u64CD\u4F5C\u5931\u8D25\uFF0C\u8BF7\u68C0\u67E5\u8F93\u5165\u3001\u8FDE\u63A5\u6216\u670D\u52A1\u7AEF\u65E5\u5FD7\u540E\u91CD\u8BD5\u3002",
|
|
123
141
|
conflict: "\u914D\u7F6E\u5DF2\u88AB\u4FEE\u6539\uFF0C\u8BF7\u5237\u65B0\u540E\u91CD\u8BD5\u3002\u672A\u63D0\u4EA4\u7684\u8868\u5355\u5185\u5BB9\u5DF2\u4FDD\u7559\u3002",
|
|
124
142
|
skillFootnote: "\u65B0\u8DEF\u5F84\u9ED8\u8BA4\u7981\u7528\u3002\u542F\u7528\u540E\uFF0CSkill provider \u4F1A\u626B\u63CF\u8BE5\u8DEF\u5F84\u4E0B\u7684\u6240\u6709\u6280\u80FD\u3002\u914D\u7F6E\u53D8\u66F4\u4F1A\u81EA\u52A8\u751F\u6548\u3002",
|
|
125
|
-
mcpFootnote: "\u65B0\u670D\u52A1\u5668\u9ED8\u8BA4\u7981\u7528\u3002\
|
|
143
|
+
mcpFootnote: "\u65B0\u670D\u52A1\u5668\u9ED8\u8BA4\u7981\u7528\u3002\u65B0\u670D\u52A1\u63A8\u8350 Streamable HTTP\uFF1BSSE \u4EC5\u7528\u4E8E\u517C\u5BB9\u65E7\u7248 /sse + /messages \u670D\u52A1\u3002\u914D\u7F6E\u53D8\u66F4\u4F1A\u81EA\u52A8\u751F\u6548\u3002",
|
|
126
144
|
secret: "\u8BF7\u52FF\u586B\u5199\u5BC6\u94A5\u6216\u5E26\u4EE4\u724C\u7684\u5730\u5740\u3002\u6B64\u7248\u672C\u6682\u4E0D\u652F\u6301\u8BA4\u8BC1\u53C2\u6570\u3002",
|
|
127
145
|
tools: "\u5DE5\u5177\u5217\u8868",
|
|
128
146
|
status_available: "\u5DE5\u5177\u53EF\u7528",
|
|
@@ -203,7 +221,8 @@ var en = {
|
|
|
203
221
|
directory: "Skill path",
|
|
204
222
|
transport: "Type",
|
|
205
223
|
stdio: "stdio",
|
|
206
|
-
http: "HTTP",
|
|
224
|
+
http: "Streamable HTTP",
|
|
225
|
+
sse: "SSE (legacy)",
|
|
207
226
|
command: "Command",
|
|
208
227
|
args: "Arguments (one per line)",
|
|
209
228
|
cwd: "Working directory (optional)",
|
|
@@ -214,10 +233,16 @@ var en = {
|
|
|
214
233
|
create: "Create",
|
|
215
234
|
edit: "Edit",
|
|
216
235
|
delete: "Delete",
|
|
236
|
+
confirmDelete: "Delete",
|
|
237
|
+
deleting: "Deleting\u2026",
|
|
217
238
|
saving: "Saving\u2026",
|
|
218
239
|
loading: "Loading\u2026",
|
|
219
|
-
|
|
220
|
-
|
|
240
|
+
deleteExtensionTitle: "Remove Skill source",
|
|
241
|
+
deleteMcpTitle: "Delete MCP server",
|
|
242
|
+
deleteSkillTitle: "Permanently delete Skill",
|
|
243
|
+
deleteExtensionConfirm: "Remove \u201C{name}\u201D from this configuration. Skill files in the directory will be kept.",
|
|
244
|
+
deleteMcpConfirm: "Delete MCP server \u201C{name}\u201D and its connection configuration.",
|
|
245
|
+
deleteSkillConfirm: "Permanently delete \u201C{name}\u201D and the resource files in its directory. This cannot be undone.",
|
|
221
246
|
enabled: "Configured enabled",
|
|
222
247
|
disabled: "Disabled",
|
|
223
248
|
enable: "Enable",
|
|
@@ -226,7 +251,7 @@ var en = {
|
|
|
226
251
|
failed: "Operation failed. Check input, connection or server logs and retry.",
|
|
227
252
|
conflict: "Configuration changed. Refresh and retry; your draft has been retained.",
|
|
228
253
|
skillFootnote: "New paths are disabled. Once enabled, the Skill provider scans every skill below them.",
|
|
229
|
-
mcpFootnote: "New servers are disabled.
|
|
254
|
+
mcpFootnote: "New servers are disabled. Prefer Streamable HTTP for new servers; SSE only supports legacy /sse + /messages endpoints.",
|
|
230
255
|
secret: "Do not enter secrets or token-bearing URLs. Authentication fields are not supported yet.",
|
|
231
256
|
tools: "Tools",
|
|
232
257
|
status_available: "Tools available",
|
|
@@ -290,7 +315,7 @@ var en = {
|
|
|
290
315
|
};
|
|
291
316
|
|
|
292
317
|
// src/client/styles.css
|
|
293
|
-
var styles_default = ".dsh-ext{color:var(--dsw-alias-label-primary);max-width:840px;padding:4px 0 28px;font:inherit}\n.dsh-ext-heading,.dsh-ext-card>header{display:flex;justify-content:space-between;align-items:center;gap:16px}\n.dsh-ext h2{font-size:22px;letter-spacing:-.4px;margin:0 0 8px}.dsh-ext h3{font-size:15px;margin:0;display:flex;align-items:center;gap:8px}\n.dsh-ext p,.dsh-ext small{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.6}.dsh-ext-heading p{margin:0}\n.dsh-ext button{font:inherit;font-size:13px;cursor:pointer;border:1px solid var(--dsw-alias-border-l4);border-radius:9px;background:var(--dsw-alias-bg-layer-2);color:inherit;padding:8px 13px;white-space:nowrap}\n.dsh-ext button:hover{background:var(--dsw-alias-bg-layer-3)}.dsh-ext button:disabled{opacity:.5;cursor:default}\n.dsh-ext :is(button,input,select,textarea):focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:3px}\n.dsh-ext-card{border:1px solid var(--dsw-alias-border-l4);border-radius:16px;margin-top:20px;background:var(--dsw-alias-bg-layer-2);overflow:hidden}\n.dsh-ext-card>header{padding:18px 20px;border-bottom:1px solid var(--dsw-alias-border-l4)}.dsh-ext-count{font-size:11px;font-weight:400;color:var(--dsw-alias-label-tertiary)}\n.dsh-ext-empty{padding:32px 20px}.dsh-ext-empty strong{font-size:14px;font-weight:500}.dsh-ext-empty p{margin:6px 0 0}\n.dsh-ext ul{list-style:none;padding:0;margin:0}.dsh-ext li{display:flex;justify-content:space-between;align-items:center;gap:14px;padding:16px 20px;border-bottom:1px solid var(--dsw-alias-border-l4)}.dsh-ext li:last-child{border:0}\n.dsh-ext-details{display:flex;flex-direction:column;gap:5px;min-width:0}.dsh-ext-details strong{font-size:14px}.dsh-ext-details code{font-size:12px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}\n.dsh-ext-runtime{display:flex;align-items:center;flex-wrap:wrap;gap:7px;font-size:12px;color:var(--dsw-alias-label-tertiary)}\n.dsh-ext-status{width:7px;height:7px;border-radius:50%;background:#858585;box-shadow:0 0 0 3px color-mix(in srgb,#858585 16%,transparent)}\n.dsh-ext-status-available{background:#38a169;box-shadow:0 0 0 3px color-mix(in srgb,#38a169 18%,transparent)}.dsh-ext-status-waiting{background:#d69e2e;box-shadow:0 0 0 3px color-mix(in srgb,#d69e2e 18%,transparent)}\n.dsh-ext-runtime details{width:100%;margin-top:2px}.dsh-ext-runtime summary{width:max-content;cursor:pointer;color:var(--dsw-alias-label-secondary);user-select:none}.dsh-ext-runtime summary:hover{color:var(--dsw-alias-label-primary)}\n.dsh-ext-runtime .dsh-ext-tools{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}.dsh-ext-runtime .dsh-ext-tools li{display:block;border:0;padding:4px 8px;border-radius:6px;background:var(--dsw-alias-bg-layer-3)}\n.dsh-ext-form{padding:20px;border-top:1px solid var(--dsw-alias-border-l4)}.dsh-ext fieldset{border:0;padding:0;margin:0;display:grid;grid-template-columns:1fr 1fr;gap:16px}.dsh-ext label{display:flex;flex-direction:column;gap:7px;font-size:13px}\n.dsh-ext input,.dsh-ext select,.dsh-ext textarea{box-sizing:border-box;min-width:0;width:100%;font:inherit;color:inherit;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l4);border-radius:8px;padding:10px}.dsh-ext textarea{resize:vertical}\n.dsh-ext-actions{display:flex;justify-content:flex-end;gap:8px;grid-column:1/-1}.dsh-ext fieldset>p{grid-column:1/-1;margin:0}.dsh-ext-notice{padding:12px 16px;border:1px solid var(--dsw-alias-border-l4);border-radius:10px}.dsh-ext-footnote{padding:0 4px}\n.dsh-ext-row-actions{display:flex;align-items:center;gap:7px;flex-wrap:wrap;justify-content:flex-end}.dsh-ext .dsh-ext-danger{color:#e98282}.dsh-ext-inline-form{width:100%;padding:14px 0 2px;border:0}.dsh-skill-edit-form{margin-top:12px;padding:14px;border-radius:10px;background:var(--dsw-alias-bg-layer-1)}\n.dsh-ext-heading-actions{display:flex;gap:8px;align-items:center}.dsh-visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n.dsh-skill-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-top:20px}.dsh-skill-stats>div{display:flex;flex-direction:column;gap:4px;padding:14px 16px;border:1px solid var(--dsw-alias-border-l4);border-radius:12px;background:var(--dsw-alias-bg-layer-2)}.dsh-skill-stats strong{font-size:20px}.dsh-skill-stats span{font-size:12px;color:var(--dsw-alias-label-tertiary)}\n.dsh-skill-search{margin-top:14px}.dsh-skill-search input{padding-left:13px}.dsh-skill-manager>.dsh-ext-form{margin-top:14px;border:1px solid var(--dsw-alias-border-l4);border-radius:14px;background:var(--dsw-alias-bg-layer-2)}\n.dsh-skill-filters{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:10px}.dsh-skill-filters label{gap:5px;font-size:11px;color:var(--dsw-alias-label-tertiary)}.dsh-skill-filters select{padding:8px 10px;color:var(--dsw-alias-label-primary)}\n.dsh-skill-source-manager{margin-top:14px}.dsh-skill-source-manager>summary{cursor:pointer;padding:14px 18px;font-size:14px;font-weight:600;user-select:none}.dsh-skill-source-manager[open]>summary{border-bottom:1px solid var(--dsw-alias-border-l4)}\n.dsh-skill-source-manager li{padding:11px 18px;flex-wrap:wrap}.dsh-skill-source-title{display:flex;align-items:flex-start;gap:11px;min-width:0}.dsh-skill-source-title>.dsh-ext-status{margin-top:6px;flex:0 0 auto}.dsh-skill-source-title>div{display:flex;min-width:0;flex-direction:column;gap:3px}.dsh-skill-source-title code{font-size:11px;color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere}.dsh-skill-source-title small{font-size:11px;color:#e98282}\n.dsh-skill-library{margin-top:14px}.dsh-skill-group-editor{display:flex;align-items:flex-end;gap:8px}.dsh-skill-group-editor label{flex:1}.dsh-skill-group-editor input{padding:7px 9px}.dsh-skill-group-editor button{padding:7px 10px}\n.dsh-skill-row{display:block!important;padding:15px 18px!important}.dsh-skill-row-main{min-width:0}.dsh-skill-name{display:flex;align-items:center;gap:9px}.dsh-skill-name strong{font-size:14px}.dsh-skill-row-main>p{margin:6px 0 3px}.dsh-skill-row-main>code{font-size:11px;color:var(--dsw-alias-label-tertiary)}\n.dsh-skill-badge{font-size:11px;padding:2px 7px;border-radius:999px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-secondary)}.dsh-skill-badge-group{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb,var(--dsw-alias-brand-primary) 12%,transparent)}.dsh-skill-badge-invalid{color:#e98282}.dsh-skill-badge-shadowed{color:#a98af8}.dsh-ext-status-invalid{background:#e05252;box-shadow:0 0 0 3px color-mix(in srgb,#e05252 18%,transparent)}.dsh-ext-status-shadowed{background:#8b5cf6;box-shadow:0 0 0 3px color-mix(in srgb,#8b5cf6 18%,transparent)}.dsh-ext-status-manual{background:#d69e2e;box-shadow:0 0 0 3px color-mix(in srgb,#d69e2e 18%,transparent)}\n.dsh-skill-row details{margin-top:9px}.dsh-skill-row summary{width:max-content;cursor:pointer;font-size:12px;color:var(--dsw-alias-label-secondary)}.dsh-skill-detail{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:12px;padding:14px;border-radius:10px;background:var(--dsw-alias-bg-layer-1)}.dsh-skill-detail>p{grid-column:1/-1;margin:0}.dsh-skill-detail p strong,.dsh-skill-detail>div>strong{display:block;margin-bottom:5px;color:var(--dsw-alias-label-secondary);font-size:12px}\n.dsh-skill-invocation{display:flex;flex-direction:column;gap:7px}.dsh-skill-invocation label,.dsh-skill-create-flags label{display:flex;flex-direction:row;align-items:center;gap:5px}.dsh-ext input[type=checkbox]{width:auto;min-width:auto}.dsh-skill-detail ul,.dsh-skill-detail li{display:block!important;margin:0;padding:0!important;border:0!important}.dsh-skill-resources{display:flex!important;flex-wrap:wrap!important;gap:5px}.dsh-skill-resources li{padding:3px 6px!important;border-radius:5px!important;background:var(--dsw-alias-bg-layer-3)!important}\n.dsh-ext-span{grid-column:1/-1}.dsh-skill-create-flags{display:flex;flex-direction:column;gap:7px;font-size:13px}.dsh-skill-create-flags>span{margin-bottom:1px}.dsh-skill-source>.dsh-ext-notice{margin:12px 18px}.dsh-skill-source>.dsh-ext-empty{padding:18px}\n@media(max-width:600px){.dsh-ext fieldset,.dsh-skill-detail,.dsh-skill-filters{grid-template-columns:1fr}.dsh-ext-heading{align-items:flex-start;flex-direction:column}.dsh-ext-heading-actions{flex-wrap:wrap}.dsh-ext-card>header{padding:14px}.dsh-ext li{padding:14px}.dsh-skill-stats{grid-template-columns:repeat(2,1fr)}.dsh-skill-group-editor{align-items:stretch;flex-direction:column}}\n";
|
|
318
|
+
var styles_default = ".dsh-ext{color:var(--dsw-alias-label-primary);max-width:840px;padding:4px 0 28px;font:inherit}\n.dsh-ext-heading,.dsh-ext-card>header{display:flex;justify-content:space-between;align-items:center;gap:16px}\n.dsh-ext h2{font-size:22px;letter-spacing:-.4px;margin:0 0 8px}.dsh-ext h3{font-size:15px;margin:0;display:flex;align-items:center;gap:8px}\n.dsh-ext p,.dsh-ext small{color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.6}.dsh-ext-heading p{margin:0}\n.dsh-ext button{font:inherit;font-size:13px;cursor:pointer;border:1px solid var(--dsw-alias-border-l4);border-radius:9px;background:var(--dsw-alias-bg-layer-2);color:inherit;padding:8px 13px;white-space:nowrap}\n.dsh-ext button:hover{background:var(--dsw-alias-bg-layer-3)}.dsh-ext button:disabled{opacity:.5;cursor:default}\n.dsh-ext :is(button,input,select,textarea):focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:3px}\n.dsh-ext-card{border:1px solid var(--dsw-alias-border-l4);border-radius:16px;margin-top:20px;background:var(--dsw-alias-bg-layer-2);overflow:hidden}\n.dsh-ext-card>header{padding:18px 20px;border-bottom:1px solid var(--dsw-alias-border-l4)}.dsh-ext-count{font-size:11px;font-weight:400;color:var(--dsw-alias-label-tertiary)}\n.dsh-ext-empty{padding:32px 20px}.dsh-ext-empty strong{font-size:14px;font-weight:500}.dsh-ext-empty p{margin:6px 0 0}\n.dsh-ext ul{list-style:none;padding:0;margin:0}.dsh-ext li{display:flex;justify-content:space-between;align-items:center;gap:14px;padding:16px 20px;border-bottom:1px solid var(--dsw-alias-border-l4)}.dsh-ext li:last-child{border:0}\n.dsh-ext-details{display:flex;flex-direction:column;gap:5px;min-width:0}.dsh-ext-details strong{font-size:14px}.dsh-ext-details code{font-size:12px;color:var(--dsw-alias-label-secondary);overflow-wrap:anywhere}\n.dsh-ext-runtime{display:flex;align-items:center;flex-wrap:wrap;gap:7px;font-size:12px;color:var(--dsw-alias-label-tertiary)}\n.dsh-ext-status{width:7px;height:7px;border-radius:50%;background:#858585;box-shadow:0 0 0 3px color-mix(in srgb,#858585 16%,transparent)}\n.dsh-ext-status-available{background:#38a169;box-shadow:0 0 0 3px color-mix(in srgb,#38a169 18%,transparent)}.dsh-ext-status-waiting{background:#d69e2e;box-shadow:0 0 0 3px color-mix(in srgb,#d69e2e 18%,transparent)}\n.dsh-ext-runtime details{width:100%;margin-top:2px}.dsh-ext-runtime summary{width:max-content;cursor:pointer;color:var(--dsw-alias-label-secondary);user-select:none}.dsh-ext-runtime summary:hover{color:var(--dsw-alias-label-primary)}\n.dsh-ext-runtime .dsh-ext-tools{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}.dsh-ext-runtime .dsh-ext-tools li{display:block;border:0;padding:4px 8px;border-radius:6px;background:var(--dsw-alias-bg-layer-3)}\n.dsh-ext-form{padding:20px;border-top:1px solid var(--dsw-alias-border-l4)}.dsh-ext fieldset{border:0;padding:0;margin:0;display:grid;grid-template-columns:1fr 1fr;gap:16px}.dsh-ext label{display:flex;flex-direction:column;gap:7px;font-size:13px}\n.dsh-ext input,.dsh-ext select,.dsh-ext textarea{box-sizing:border-box;min-width:0;width:100%;font:inherit;color:inherit;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l4);border-radius:8px;padding:10px}.dsh-ext textarea{resize:vertical}\n.dsh-ext-actions{display:flex;justify-content:flex-end;gap:8px;grid-column:1/-1}.dsh-ext fieldset>p{grid-column:1/-1;margin:0}.dsh-ext-notice{padding:12px 16px;border:1px solid var(--dsw-alias-border-l4);border-radius:10px}.dsh-ext-footnote{padding:0 4px}\n.dsh-ext-row-actions{display:flex;align-items:center;gap:7px;flex-wrap:wrap;justify-content:flex-end}.dsh-ext .dsh-ext-danger{color:#e98282}.dsh-ext-inline-form{width:100%;padding:14px 0 2px;border:0}.dsh-skill-edit-form{margin-top:12px;padding:14px;border-radius:10px;background:var(--dsw-alias-bg-layer-1)}\n.dsh-confirm{box-sizing:border-box;width:min(430px,calc(100vw - 32px));padding:22px;border:1px solid var(--dsw-alias-border-l4);border-radius:16px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);box-shadow:0 20px 60px rgba(0,0,0,.28);font:inherit}.dsh-confirm::backdrop{background:rgba(10,12,16,.48);backdrop-filter:blur(3px)}\n.dsh-confirm-icon{display:grid;place-items:center;width:34px;height:34px;margin-bottom:16px;border-radius:10px;background:color-mix(in srgb,#e05252 14%,transparent);color:#e98282;font-size:19px;font-weight:700}.dsh-confirm-copy h3{margin:0;font-size:16px}.dsh-confirm-copy p{margin:8px 0 0;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.65}.dsh-confirm-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:22px}.dsh-confirm button{font:inherit;font-size:13px;cursor:pointer;border:1px solid var(--dsw-alias-border-l4);border-radius:9px;background:var(--dsw-alias-bg-layer-2);color:inherit;padding:8px 14px}.dsh-confirm button:hover{background:var(--dsw-alias-bg-layer-3)}.dsh-confirm button:disabled{opacity:.5;cursor:default}.dsh-confirm button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:3px}.dsh-confirm .dsh-confirm-delete{border-color:color-mix(in srgb,#e05252 45%,var(--dsw-alias-border-l4));background:#b83232;color:#fff}.dsh-confirm .dsh-confirm-delete:hover{background:#c63b3b}\n.dsh-ext-heading-actions{display:flex;gap:8px;align-items:center}.dsh-visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}\n.dsh-skill-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-top:20px}.dsh-skill-stats>div{display:flex;flex-direction:column;gap:4px;padding:14px 16px;border:1px solid var(--dsw-alias-border-l4);border-radius:12px;background:var(--dsw-alias-bg-layer-2)}.dsh-skill-stats strong{font-size:20px}.dsh-skill-stats span{font-size:12px;color:var(--dsw-alias-label-tertiary)}\n.dsh-skill-search{margin-top:14px}.dsh-skill-search input{padding-left:13px}.dsh-skill-manager>.dsh-ext-form{margin-top:14px;border:1px solid var(--dsw-alias-border-l4);border-radius:14px;background:var(--dsw-alias-bg-layer-2)}\n.dsh-skill-filters{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:10px}.dsh-skill-filters label{gap:5px;font-size:11px;color:var(--dsw-alias-label-tertiary)}.dsh-skill-filters select{padding:8px 10px;color:var(--dsw-alias-label-primary)}\n.dsh-skill-source-manager{margin-top:14px}.dsh-skill-source-manager>summary{cursor:pointer;padding:14px 18px;font-size:14px;font-weight:600;user-select:none}.dsh-skill-source-manager[open]>summary{border-bottom:1px solid var(--dsw-alias-border-l4)}\n.dsh-skill-source-manager li{padding:11px 18px;flex-wrap:wrap}.dsh-skill-source-title{display:flex;align-items:flex-start;gap:11px;min-width:0}.dsh-skill-source-title>.dsh-ext-status{margin-top:6px;flex:0 0 auto}.dsh-skill-source-title>div{display:flex;min-width:0;flex-direction:column;gap:3px}.dsh-skill-source-title code{font-size:11px;color:var(--dsw-alias-label-tertiary);overflow-wrap:anywhere}.dsh-skill-source-title small{font-size:11px;color:#e98282}\n.dsh-skill-library{margin-top:14px}.dsh-skill-group-editor{display:flex;align-items:flex-end;gap:8px}.dsh-skill-group-editor label{flex:1}.dsh-skill-group-editor input{padding:7px 9px}.dsh-skill-group-editor button{padding:7px 10px}\n.dsh-skill-row{display:block!important;padding:15px 18px!important}.dsh-skill-row-main{min-width:0}.dsh-skill-name{display:flex;align-items:center;gap:9px}.dsh-skill-name strong{font-size:14px}.dsh-skill-row-main>p{margin:6px 0 3px}.dsh-skill-row-main>code{font-size:11px;color:var(--dsw-alias-label-tertiary)}\n.dsh-skill-badge{font-size:11px;padding:2px 7px;border-radius:999px;background:var(--dsw-alias-bg-layer-3);color:var(--dsw-alias-label-secondary)}.dsh-skill-badge-group{color:var(--dsw-alias-brand-primary);background:color-mix(in srgb,var(--dsw-alias-brand-primary) 12%,transparent)}.dsh-skill-badge-invalid{color:#e98282}.dsh-skill-badge-shadowed{color:#a98af8}.dsh-ext-status-invalid{background:#e05252;box-shadow:0 0 0 3px color-mix(in srgb,#e05252 18%,transparent)}.dsh-ext-status-shadowed{background:#8b5cf6;box-shadow:0 0 0 3px color-mix(in srgb,#8b5cf6 18%,transparent)}.dsh-ext-status-manual{background:#d69e2e;box-shadow:0 0 0 3px color-mix(in srgb,#d69e2e 18%,transparent)}\n.dsh-skill-row details{margin-top:9px}.dsh-skill-row summary{width:max-content;cursor:pointer;font-size:12px;color:var(--dsw-alias-label-secondary)}.dsh-skill-detail{display:grid;grid-template-columns:1fr 1fr;gap:14px;margin-top:12px;padding:14px;border-radius:10px;background:var(--dsw-alias-bg-layer-1)}.dsh-skill-detail>p{grid-column:1/-1;margin:0}.dsh-skill-detail p strong,.dsh-skill-detail>div>strong{display:block;margin-bottom:5px;color:var(--dsw-alias-label-secondary);font-size:12px}\n.dsh-skill-invocation{display:flex;flex-direction:column;gap:7px}.dsh-skill-invocation label,.dsh-skill-create-flags label{display:flex;flex-direction:row;align-items:center;gap:5px}.dsh-ext input[type=checkbox]{width:auto;min-width:auto}.dsh-skill-detail ul,.dsh-skill-detail li{display:block!important;margin:0;padding:0!important;border:0!important}.dsh-skill-resources{display:flex!important;flex-wrap:wrap!important;gap:5px}.dsh-skill-resources li{padding:3px 6px!important;border-radius:5px!important;background:var(--dsw-alias-bg-layer-3)!important}\n.dsh-ext-span{grid-column:1/-1}.dsh-skill-create-flags{display:flex;flex-direction:column;gap:7px;font-size:13px}.dsh-skill-create-flags>span{margin-bottom:1px}.dsh-skill-source>.dsh-ext-notice{margin:12px 18px}.dsh-skill-source>.dsh-ext-empty{padding:18px}\n@media(max-width:600px){.dsh-ext fieldset,.dsh-skill-detail,.dsh-skill-filters{grid-template-columns:1fr}.dsh-ext-heading{align-items:flex-start;flex-direction:column}.dsh-ext-heading-actions{flex-wrap:wrap}.dsh-ext-card>header{padding:14px}.dsh-ext li{padding:14px}.dsh-skill-stats{grid-template-columns:repeat(2,1fr)}.dsh-skill-group-editor{align-items:stretch;flex-direction:column}}\n";
|
|
294
319
|
|
|
295
320
|
// src/client/index.jsx
|
|
296
321
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -321,7 +346,7 @@ function apply(ctx) {
|
|
|
321
346
|
const t = ctx.locale.bind(namespace);
|
|
322
347
|
const request = async (endpoint, args, options) => {
|
|
323
348
|
const ok = await controller.request(endpoint, args, options);
|
|
324
|
-
if (ok && (endpoint
|
|
349
|
+
if (ok && refreshesSlashCatalog(endpoint, options)) refreshSlashCatalog();
|
|
325
350
|
return ok;
|
|
326
351
|
};
|
|
327
352
|
const face = { hooks: { manager: controller }, request };
|
|
@@ -368,7 +393,7 @@ function SkillSection({ t, useManager, request }) {
|
|
|
368
393
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ext-heading-actions", children: [
|
|
369
394
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy || !state.revision, onClick: () => setForm("source"), children: t("add") }),
|
|
370
395
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy || sources.length === 0, onClick: () => setForm("skill"), children: t("newSkill") }),
|
|
371
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy, onClick: () => request("list"), children: t("refresh") })
|
|
396
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy, onClick: () => request("list", {}, { refreshSlashCatalog: true }), children: t("refresh") })
|
|
372
397
|
] })
|
|
373
398
|
] }),
|
|
374
399
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PageState, { state, t }),
|
|
@@ -446,51 +471,70 @@ function SkillSection({ t, useManager, request }) {
|
|
|
446
471
|
}
|
|
447
472
|
function SkillSources({ sources, skills, t, busy, request }) {
|
|
448
473
|
const [editing, setEditing] = (0, import_react.useState)("");
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `dsh-ext-status ${source.enabled ? "dsh-ext-status-available" : ""}`, "aria-hidden": "true" }),
|
|
458
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
459
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("strong", { children: [
|
|
460
|
-
source.id,
|
|
461
|
-
" ",
|
|
462
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-ext-count", children: skills.filter((skill) => skill.sourceId === source.id).length })
|
|
463
|
-
] }),
|
|
464
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: source.location }),
|
|
465
|
-
source.issue && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: t(`issue_${source.issue}`) })
|
|
466
|
-
] })
|
|
474
|
+
const [confirming, setConfirming] = (0, import_react.useState)("");
|
|
475
|
+
const target = sources.find((source) => source.id === confirming);
|
|
476
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
477
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "dsh-ext-card dsh-skill-source-manager", children: [
|
|
478
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("summary", { children: [
|
|
479
|
+
t("skillSources"),
|
|
480
|
+
" ",
|
|
481
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-ext-count", children: sources.length })
|
|
467
482
|
] }),
|
|
468
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
469
|
-
/* @__PURE__ */ (0, import_jsx_runtime.
|
|
470
|
-
|
|
471
|
-
"
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
483
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { children: sources.map((source) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
|
|
484
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-skill-source-title", children: [
|
|
485
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `dsh-ext-status ${source.enabled ? "dsh-ext-status-available" : ""}`, "aria-hidden": "true" }),
|
|
486
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
487
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("strong", { children: [
|
|
488
|
+
source.id,
|
|
489
|
+
" ",
|
|
490
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-ext-count", children: skills.filter((skill) => skill.sourceId === source.id).length })
|
|
491
|
+
] }),
|
|
492
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: source.location }),
|
|
493
|
+
source.issue && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: t(`issue_${source.issue}`) })
|
|
494
|
+
] })
|
|
495
|
+
] }),
|
|
496
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ext-row-actions", children: [
|
|
497
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy, onClick: () => setEditing(editing === source.id ? "" : source.id), children: t("edit") }),
|
|
498
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
499
|
+
"button",
|
|
500
|
+
{
|
|
501
|
+
type: "button",
|
|
502
|
+
role: "switch",
|
|
503
|
+
"aria-checked": source.enabled,
|
|
504
|
+
disabled: busy,
|
|
505
|
+
onClick: () => request("enable", { id: source.id, enabled: !source.enabled }, { refreshSlashCatalog: true }),
|
|
506
|
+
children: t(source.enabled ? "disable" : "enable")
|
|
507
|
+
}
|
|
508
|
+
),
|
|
509
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dsh-ext-danger", type: "button", disabled: busy, onClick: () => setConfirming(source.id), children: t("delete") })
|
|
510
|
+
] }),
|
|
511
|
+
editing === source.id && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SkillSourceForm, { source, t, busy, onCancel: () => setEditing(""), onSave: async (args) => {
|
|
512
|
+
if (await request("update-skill-source", args)) setEditing("");
|
|
513
|
+
} })
|
|
514
|
+
] }, source.id)) })
|
|
515
|
+
] }),
|
|
516
|
+
target && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
517
|
+
ConfirmDialog,
|
|
518
|
+
{
|
|
519
|
+
title: t("deleteExtensionTitle"),
|
|
520
|
+
message: t("deleteExtensionConfirm").replace("{name}", target.id),
|
|
521
|
+
t,
|
|
522
|
+
busy,
|
|
523
|
+
onCancel: () => setConfirming(""),
|
|
524
|
+
onConfirm: async () => {
|
|
525
|
+
if (await request("delete-extension", { id: target.id }, { refreshSlashCatalog: true })) {
|
|
526
|
+
setEditing("");
|
|
527
|
+
setConfirming("");
|
|
479
528
|
}
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
}, children: t("delete") })
|
|
484
|
-
] }),
|
|
485
|
-
editing === source.id && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SkillSourceForm, { source, t, busy, onCancel: () => setEditing(""), onSave: async (args) => {
|
|
486
|
-
if (await request("update-skill-source", args)) setEditing("");
|
|
487
|
-
} })
|
|
488
|
-
] }, source.id)) })
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
)
|
|
489
532
|
] });
|
|
490
533
|
}
|
|
491
534
|
function McpSection({ t, useManager, request }) {
|
|
492
535
|
const state = useManager((value) => value);
|
|
493
536
|
const [form, setForm] = (0, import_react.useState)("");
|
|
537
|
+
const [confirming, setConfirming] = (0, import_react.useState)("");
|
|
494
538
|
const busy = state.loading || state.saving;
|
|
495
539
|
const rows = state.extensions.filter((row) => row.kind === "mcp");
|
|
496
540
|
useLiveRefresh(request);
|
|
@@ -539,16 +583,30 @@ function McpSection({ t, useManager, request }) {
|
|
|
539
583
|
children: t(row.enabled ? "disable" : "enable")
|
|
540
584
|
}
|
|
541
585
|
),
|
|
542
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dsh-ext-danger", type: "button", disabled: busy, onClick:
|
|
543
|
-
if (window.confirm(t("deleteExtensionConfirm")) && await request("delete-extension", { id: row.id })) setForm("");
|
|
544
|
-
}, children: t("delete") })
|
|
586
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dsh-ext-danger", type: "button", disabled: busy, onClick: () => setConfirming(row.id), children: t("delete") })
|
|
545
587
|
] })
|
|
546
588
|
] }, row.id)) }),
|
|
547
589
|
form === "new" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(McpForm, { t, busy, onCancel: () => setForm(""), onSave: async (args) => {
|
|
548
590
|
if (await request("add-mcp", args)) setForm("");
|
|
549
591
|
} })
|
|
550
592
|
] }),
|
|
551
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-ext-footnote", children: t("mcpFootnote") })
|
|
593
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-ext-footnote", children: t("mcpFootnote") }),
|
|
594
|
+
confirming && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
595
|
+
ConfirmDialog,
|
|
596
|
+
{
|
|
597
|
+
title: t("deleteMcpTitle"),
|
|
598
|
+
message: t("deleteMcpConfirm").replace("{name}", confirming),
|
|
599
|
+
t,
|
|
600
|
+
busy,
|
|
601
|
+
onCancel: () => setConfirming(""),
|
|
602
|
+
onConfirm: async () => {
|
|
603
|
+
if (await request("delete-extension", { id: confirming })) {
|
|
604
|
+
setForm("");
|
|
605
|
+
setConfirming("");
|
|
606
|
+
}
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
)
|
|
552
610
|
] });
|
|
553
611
|
}
|
|
554
612
|
function useLiveRefresh(request) {
|
|
@@ -582,6 +640,7 @@ function skillState(skill) {
|
|
|
582
640
|
}
|
|
583
641
|
function SkillRow({ skill, source, groups, t, busy, request }) {
|
|
584
642
|
const [editing, setEditing] = (0, import_react.useState)(false);
|
|
643
|
+
const [confirming, setConfirming] = (0, import_react.useState)(false);
|
|
585
644
|
const state = skillState(skill);
|
|
586
645
|
const invocation = skill.modelInvocable && skill.userInvocable ? "both" : skill.modelInvocable ? "modelOnly" : skill.userInvocable ? "manualOnly" : "none";
|
|
587
646
|
const statusLabel = !skill.valid ? "skillStatusInvalid" : !skill.sourceEnabled ? "skillStatusSourceDisabled" : skill.shadowed ? "skillStatusShadowed" : "";
|
|
@@ -639,21 +698,72 @@ function SkillRow({ skill, source, groups, t, busy, request }) {
|
|
|
639
698
|
] }),
|
|
640
699
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ext-row-actions", children: [
|
|
641
700
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy || !skill.valid, onClick: () => setEditing(!editing), children: t("edit") }),
|
|
642
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dsh-ext-danger", type: "button", disabled: busy || !skill.valid, onClick: () =>
|
|
643
|
-
if (window.confirm(t("deleteSkillConfirm"))) void request("delete-skill", {
|
|
644
|
-
sourceId: skill.sourceId,
|
|
645
|
-
relativePath: skill.relativePath,
|
|
646
|
-
skillRevision: skill.revision
|
|
647
|
-
});
|
|
648
|
-
}, children: t("delete") })
|
|
701
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dsh-ext-danger", type: "button", disabled: busy || !skill.valid, onClick: () => setConfirming(true), children: t("delete") })
|
|
649
702
|
] })
|
|
650
703
|
] })
|
|
651
704
|
] }),
|
|
652
705
|
editing && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SkillEditForm, { skill, t, busy, onCancel: () => setEditing(false), onSave: async (args) => {
|
|
653
706
|
if (await request("update-skill", args)) setEditing(false);
|
|
654
|
-
} })
|
|
707
|
+
} }),
|
|
708
|
+
confirming && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
709
|
+
ConfirmDialog,
|
|
710
|
+
{
|
|
711
|
+
title: t("deleteSkillTitle"),
|
|
712
|
+
message: t("deleteSkillConfirm").replace("{name}", skill.name),
|
|
713
|
+
t,
|
|
714
|
+
busy,
|
|
715
|
+
onCancel: () => setConfirming(false),
|
|
716
|
+
onConfirm: async () => {
|
|
717
|
+
await request("delete-skill", {
|
|
718
|
+
sourceId: skill.sourceId,
|
|
719
|
+
relativePath: skill.relativePath,
|
|
720
|
+
skillRevision: skill.revision
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
)
|
|
655
725
|
] }) });
|
|
656
726
|
}
|
|
727
|
+
function ConfirmDialog({ title, message, t, busy, onConfirm, onCancel }) {
|
|
728
|
+
const dialog = (0, import_react.useRef)(null);
|
|
729
|
+
const cancel = (0, import_react.useRef)(null);
|
|
730
|
+
const titleId = (0, import_react.useId)();
|
|
731
|
+
const messageId = (0, import_react.useId)();
|
|
732
|
+
(0, import_react.useEffect)(() => {
|
|
733
|
+
dialog.current.showModal();
|
|
734
|
+
cancel.current.focus();
|
|
735
|
+
return () => {
|
|
736
|
+
if (dialog.current?.open) dialog.current.close();
|
|
737
|
+
};
|
|
738
|
+
}, []);
|
|
739
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
740
|
+
"dialog",
|
|
741
|
+
{
|
|
742
|
+
ref: dialog,
|
|
743
|
+
className: "dsh-confirm",
|
|
744
|
+
"aria-labelledby": titleId,
|
|
745
|
+
"aria-describedby": messageId,
|
|
746
|
+
onCancel: (event) => {
|
|
747
|
+
event.preventDefault();
|
|
748
|
+
if (!busy) onCancel();
|
|
749
|
+
},
|
|
750
|
+
onMouseDown: (event) => {
|
|
751
|
+
if (event.target === event.currentTarget && !busy) onCancel();
|
|
752
|
+
},
|
|
753
|
+
children: [
|
|
754
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-confirm-icon", "aria-hidden": "true", children: "!" }),
|
|
755
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-confirm-copy", children: [
|
|
756
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { id: titleId, children: title }),
|
|
757
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { id: messageId, children: message })
|
|
758
|
+
] }),
|
|
759
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-confirm-actions", children: [
|
|
760
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { ref: cancel, type: "button", disabled: busy, onClick: onCancel, children: t("cancel") }),
|
|
761
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dsh-confirm-delete", type: "button", disabled: busy, onClick: () => void onConfirm(), children: t(busy ? "deleting" : "confirmDelete") })
|
|
762
|
+
] })
|
|
763
|
+
]
|
|
764
|
+
}
|
|
765
|
+
);
|
|
766
|
+
}
|
|
657
767
|
function SkillEditForm({ skill, t, busy, onSave, onCancel }) {
|
|
658
768
|
const prefix = (0, import_react.useId)();
|
|
659
769
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("form", { className: "dsh-skill-edit-form", onSubmit: (event) => {
|
|
@@ -837,7 +947,8 @@ function McpForm({ row, t, busy, onSave, onCancel }) {
|
|
|
837
947
|
t("transport"),
|
|
838
948
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { id: `${prefix}-transport`, value: transport, onChange: (event) => setTransport(event.target.value), children: [
|
|
839
949
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "stdio", children: t("stdio") }),
|
|
840
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "streamable-http", children: t("http") })
|
|
950
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "streamable-http", children: t("http") }),
|
|
951
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "sse", children: t("sse") })
|
|
841
952
|
] })
|
|
842
953
|
] }),
|
|
843
954
|
transport === "stdio" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
|
@@ -874,7 +985,8 @@ function AddForm({ kind, t, busy, onSave, onCancel }) {
|
|
|
874
985
|
t("transport"),
|
|
875
986
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { id: `${prefix}-transport`, value: transport, onChange: (event) => setTransport(event.target.value), children: [
|
|
876
987
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "stdio", children: t("stdio") }),
|
|
877
|
-
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "streamable-http", children: t("http") })
|
|
988
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "streamable-http", children: t("http") }),
|
|
989
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "sse", children: t("sse") })
|
|
878
990
|
] })
|
|
879
991
|
] }),
|
|
880
992
|
transport === "stdio" ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@civilization/deepseek-harness-skill-mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Skill path and MCP server management for DeepSeek Harness Web",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"exports": {
|
|
19
19
|
".": "./src/index.js",
|
|
20
20
|
"./client": "./lib/client.js",
|
|
21
|
+
"./mcp-sse": "./src/mcp-sse.js",
|
|
21
22
|
"./package.json": "./package.json"
|
|
22
23
|
},
|
|
23
24
|
"files": [
|
|
@@ -52,6 +53,7 @@
|
|
|
52
53
|
"@deepseek-ai/dsh-mcp-client": "0.1.2-rc.1",
|
|
53
54
|
"@deepseek-ai/dsh-skill-filesystem": "0.1.2-rc.1",
|
|
54
55
|
"@deepseek-ai/schemastery": "3.18.2",
|
|
56
|
+
"@modelcontextprotocol/sdk": "1.29.0",
|
|
55
57
|
"yaml": "^2.9.0"
|
|
56
58
|
},
|
|
57
59
|
"peerDependencies": {
|
|
@@ -63,7 +65,6 @@
|
|
|
63
65
|
"@deepseek-ai/dsh-skill": "0.1.2-rc.1",
|
|
64
66
|
"@deepseek-ai/dsh-system-prompt": "0.1.2-rc.1",
|
|
65
67
|
"@deepseek-ai/dsh-tools": "0.1.2-rc.1",
|
|
66
|
-
"@modelcontextprotocol/sdk": "1.29.0",
|
|
67
68
|
"esbuild": "^0.28.2",
|
|
68
69
|
"react": "^19.2.8"
|
|
69
70
|
}
|
package/src/client/controller.js
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
/** One observable per mounted plugin; drafts stay in the settings component. */
|
|
2
|
+
const skillCatalogMutations = new Set([
|
|
3
|
+
'add-skill', 'update-skill-source', 'create-skill', 'update-skill', 'delete-skill', 'set-skill-invocation',
|
|
4
|
+
])
|
|
5
|
+
|
|
6
|
+
/** Keep broad connection cache invalidation away from ordinary page reads and MCP-only writes. */
|
|
7
|
+
export function refreshesSlashCatalog(endpoint, options = {}) {
|
|
8
|
+
return options.refreshSlashCatalog === true || skillCatalogMutations.has(endpoint)
|
|
9
|
+
}
|
|
10
|
+
|
|
2
11
|
export class ExtensionsController {
|
|
3
12
|
state = { extensions: [], sources: [], skills: [], revision: '', loading: false, saving: false, error: '', saved: false }
|
|
4
13
|
listeners = new Set()
|
package/src/client/index.jsx
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/** Skill and MCP settings sections registered through the public Slots and Connection services. */
|
|
2
|
-
import { useEffect, useId, useState } from 'react'
|
|
3
|
-
import { ExtensionsController } from './controller.js'
|
|
2
|
+
import { useEffect, useId, useRef, useState } from 'react'
|
|
3
|
+
import { ExtensionsController, refreshesSlashCatalog } from './controller.js'
|
|
4
4
|
import { zh, en } from './locales.js'
|
|
5
5
|
import css from './styles.css'
|
|
6
6
|
|
|
@@ -27,7 +27,7 @@ export function apply(ctx) {
|
|
|
27
27
|
const t = ctx.locale.bind(namespace)
|
|
28
28
|
const request = async (endpoint, args, options) => {
|
|
29
29
|
const ok = await controller.request(endpoint, args, options)
|
|
30
|
-
if (ok && (endpoint
|
|
30
|
+
if (ok && refreshesSlashCatalog(endpoint, options)) refreshSlashCatalog()
|
|
31
31
|
return ok
|
|
32
32
|
}
|
|
33
33
|
const face = { hooks: { manager: controller }, request }
|
|
@@ -70,7 +70,7 @@ export function SkillSection({ t, useManager, request }) {
|
|
|
70
70
|
<header className="dsh-ext-heading"><div><h2>{t('navSkill')}</h2><p>{t('skillIntro')}</p></div>
|
|
71
71
|
<div className="dsh-ext-heading-actions"><button type="button" disabled={busy || !state.revision} onClick={() => setForm('source')}>{t('add')}</button>
|
|
72
72
|
<button type="button" disabled={busy || sources.length === 0} onClick={() => setForm('skill')}>{t('newSkill')}</button>
|
|
73
|
-
<button type="button" disabled={busy} onClick={() => request('list')}>{t('refresh')}</button></div></header>
|
|
73
|
+
<button type="button" disabled={busy} onClick={() => request('list', {}, { refreshSlashCatalog: true })}>{t('refresh')}</button></div></header>
|
|
74
74
|
<PageState state={state} t={t} />
|
|
75
75
|
<div className="dsh-skill-stats">
|
|
76
76
|
{Object.entries(counts).map(([key, value]) => <div key={key}><strong>{value}</strong><span>{t(`stat_${key}`)}</span></div>)}
|
|
@@ -107,25 +107,29 @@ export function SkillSection({ t, useManager, request }) {
|
|
|
107
107
|
|
|
108
108
|
function SkillSources({ sources, skills, t, busy, request }) {
|
|
109
109
|
const [editing, setEditing] = useState('')
|
|
110
|
-
|
|
110
|
+
const [confirming, setConfirming] = useState('')
|
|
111
|
+
const target = sources.find(source => source.id === confirming)
|
|
112
|
+
return <><details className="dsh-ext-card dsh-skill-source-manager"><summary>{t('skillSources')} <span className="dsh-ext-count">{sources.length}</span></summary>
|
|
111
113
|
<ul>{sources.map(source => <li key={source.id}><div className="dsh-skill-source-title"><span className={`dsh-ext-status ${source.enabled ? 'dsh-ext-status-available' : ''}`} aria-hidden="true" />
|
|
112
114
|
<div><strong>{source.id} <span className="dsh-ext-count">{skills.filter(skill => skill.sourceId === source.id).length}</span></strong><code>{source.location}</code>
|
|
113
115
|
{source.issue && <small>{t(`issue_${source.issue}`)}</small>}</div></div>
|
|
114
116
|
<div className="dsh-ext-row-actions"><button type="button" disabled={busy} onClick={() => setEditing(editing === source.id ? '' : source.id)}>{t('edit')}</button>
|
|
115
117
|
<button type="button" role="switch" aria-checked={source.enabled} disabled={busy}
|
|
116
|
-
onClick={() => request('enable', { id: source.id, enabled: !source.enabled })}>{t(source.enabled ? 'disable' : 'enable')}</button>
|
|
117
|
-
<button className="dsh-ext-danger" type="button" disabled={busy} onClick={
|
|
118
|
-
if (window.confirm(t('deleteExtensionConfirm')) && await request('delete-extension', { id: source.id })) setEditing('')
|
|
119
|
-
}}>{t('delete')}</button></div>
|
|
118
|
+
onClick={() => request('enable', { id: source.id, enabled: !source.enabled }, { refreshSlashCatalog: true })}>{t(source.enabled ? 'disable' : 'enable')}</button>
|
|
119
|
+
<button className="dsh-ext-danger" type="button" disabled={busy} onClick={() => setConfirming(source.id)}>{t('delete')}</button></div>
|
|
120
120
|
{editing === source.id && <SkillSourceForm source={source} t={t} busy={busy} onCancel={() => setEditing('')} onSave={async args => {
|
|
121
121
|
if (await request('update-skill-source', args)) setEditing('')
|
|
122
122
|
}} />}</li>)}</ul>
|
|
123
|
-
</details>
|
|
123
|
+
</details>{target && <ConfirmDialog title={t('deleteExtensionTitle')} message={t('deleteExtensionConfirm').replace('{name}', target.id)}
|
|
124
|
+
t={t} busy={busy} onCancel={() => setConfirming('')} onConfirm={async () => {
|
|
125
|
+
if (await request('delete-extension', { id: target.id }, { refreshSlashCatalog: true })) { setEditing(''); setConfirming('') }
|
|
126
|
+
}} />}</>
|
|
124
127
|
}
|
|
125
128
|
|
|
126
129
|
export function McpSection({ t, useManager, request }) {
|
|
127
130
|
const state = useManager(value => value)
|
|
128
131
|
const [form, setForm] = useState('')
|
|
132
|
+
const [confirming, setConfirming] = useState('')
|
|
129
133
|
const busy = state.loading || state.saving
|
|
130
134
|
const rows = state.extensions.filter(row => row.kind === 'mcp')
|
|
131
135
|
useLiveRefresh(request)
|
|
@@ -144,14 +148,16 @@ export function McpSection({ t, useManager, request }) {
|
|
|
144
148
|
<div className="dsh-ext-row-actions"><button type="button" disabled={busy} onClick={() => setForm(form === row.id ? '' : row.id)}>{t('edit')}</button>
|
|
145
149
|
<button type="button" role="switch" aria-checked={row.enabled} aria-label={`${t(row.enabled ? 'disable' : 'enable')} ${row.id}`} disabled={busy}
|
|
146
150
|
onClick={() => request('enable', { id: row.id, enabled: !row.enabled })}>{t(row.enabled ? 'disable' : 'enable')}</button>
|
|
147
|
-
<button className="dsh-ext-danger" type="button" disabled={busy} onClick={
|
|
148
|
-
if (window.confirm(t('deleteExtensionConfirm')) && await request('delete-extension', { id: row.id })) setForm('')
|
|
149
|
-
}}>{t('delete')}</button></div></li>)}</ul>
|
|
151
|
+
<button className="dsh-ext-danger" type="button" disabled={busy} onClick={() => setConfirming(row.id)}>{t('delete')}</button></div></li>)}</ul>
|
|
150
152
|
{form === 'new' && <McpForm t={t} busy={busy} onCancel={() => setForm('')} onSave={async args => {
|
|
151
153
|
if (await request('add-mcp', args)) setForm('')
|
|
152
154
|
}} />}
|
|
153
155
|
</section>
|
|
154
156
|
<p className="dsh-ext-footnote">{t('mcpFootnote')}</p>
|
|
157
|
+
{confirming && <ConfirmDialog title={t('deleteMcpTitle')} message={t('deleteMcpConfirm').replace('{name}', confirming)}
|
|
158
|
+
t={t} busy={busy} onCancel={() => setConfirming('')} onConfirm={async () => {
|
|
159
|
+
if (await request('delete-extension', { id: confirming })) { setForm(''); setConfirming('') }
|
|
160
|
+
}} />}
|
|
155
161
|
</section>
|
|
156
162
|
}
|
|
157
163
|
|
|
@@ -188,6 +194,7 @@ function skillState(skill) {
|
|
|
188
194
|
|
|
189
195
|
function SkillRow({ skill, source, groups, t, busy, request }) {
|
|
190
196
|
const [editing, setEditing] = useState(false)
|
|
197
|
+
const [confirming, setConfirming] = useState(false)
|
|
191
198
|
const state = skillState(skill)
|
|
192
199
|
const invocation = skill.modelInvocable && skill.userInvocable ? 'both'
|
|
193
200
|
: skill.modelInvocable ? 'modelOnly' : skill.userInvocable ? 'manualOnly' : 'none'
|
|
@@ -214,18 +221,41 @@ function SkillRow({ skill, source, groups, t, busy, request }) {
|
|
|
214
221
|
? <ul className="dsh-skill-resources">{skill.resources.map(path => <li key={path}><code>{path}</code></li>)}</ul>
|
|
215
222
|
: <p>{t('noResources')}</p>}</div>
|
|
216
223
|
<div className="dsh-ext-row-actions"><button type="button" disabled={busy || !skill.valid} onClick={() => setEditing(!editing)}>{t('edit')}</button>
|
|
217
|
-
<button className="dsh-ext-danger" type="button" disabled={busy || !skill.valid} onClick={() => {
|
|
218
|
-
if (window.confirm(t('deleteSkillConfirm'))) void request('delete-skill', {
|
|
219
|
-
sourceId: skill.sourceId, relativePath: skill.relativePath, skillRevision: skill.revision,
|
|
220
|
-
})
|
|
221
|
-
}}>{t('delete')}</button></div>
|
|
224
|
+
<button className="dsh-ext-danger" type="button" disabled={busy || !skill.valid} onClick={() => setConfirming(true)}>{t('delete')}</button></div>
|
|
222
225
|
</div></details>
|
|
223
226
|
{editing && <SkillEditForm skill={skill} t={t} busy={busy} onCancel={() => setEditing(false)} onSave={async args => {
|
|
224
227
|
if (await request('update-skill', args)) setEditing(false)
|
|
225
228
|
}} />}
|
|
229
|
+
{confirming && <ConfirmDialog title={t('deleteSkillTitle')} message={t('deleteSkillConfirm').replace('{name}', skill.name)}
|
|
230
|
+
t={t} busy={busy} onCancel={() => setConfirming(false)} onConfirm={async () => {
|
|
231
|
+
await request('delete-skill', {
|
|
232
|
+
sourceId: skill.sourceId, relativePath: skill.relativePath, skillRevision: skill.revision,
|
|
233
|
+
})
|
|
234
|
+
}} />}
|
|
226
235
|
</div></li>
|
|
227
236
|
}
|
|
228
237
|
|
|
238
|
+
function ConfirmDialog({ title, message, t, busy, onConfirm, onCancel }) {
|
|
239
|
+
const dialog = useRef(null)
|
|
240
|
+
const cancel = useRef(null)
|
|
241
|
+
const titleId = useId()
|
|
242
|
+
const messageId = useId()
|
|
243
|
+
useEffect(() => {
|
|
244
|
+
dialog.current.showModal()
|
|
245
|
+
cancel.current.focus()
|
|
246
|
+
return () => { if (dialog.current?.open) dialog.current.close() }
|
|
247
|
+
}, [])
|
|
248
|
+
return <dialog ref={dialog} className="dsh-confirm" aria-labelledby={titleId} aria-describedby={messageId}
|
|
249
|
+
onCancel={event => { event.preventDefault(); if (!busy) onCancel() }} onMouseDown={event => {
|
|
250
|
+
if (event.target === event.currentTarget && !busy) onCancel()
|
|
251
|
+
}}>
|
|
252
|
+
<div className="dsh-confirm-icon" aria-hidden="true">!</div>
|
|
253
|
+
<div className="dsh-confirm-copy"><h3 id={titleId}>{title}</h3><p id={messageId}>{message}</p></div>
|
|
254
|
+
<div className="dsh-confirm-actions"><button ref={cancel} type="button" disabled={busy} onClick={onCancel}>{t('cancel')}</button>
|
|
255
|
+
<button className="dsh-confirm-delete" type="button" disabled={busy} onClick={() => void onConfirm()}>{t(busy ? 'deleting' : 'confirmDelete')}</button></div>
|
|
256
|
+
</dialog>
|
|
257
|
+
}
|
|
258
|
+
|
|
229
259
|
function SkillEditForm({ skill, t, busy, onSave, onCancel }) {
|
|
230
260
|
const prefix = useId()
|
|
231
261
|
return <form className="dsh-skill-edit-form" onSubmit={event => {
|
|
@@ -326,7 +356,7 @@ function McpForm({ row, t, busy, onSave, onCancel }) {
|
|
|
326
356
|
void onSave({ id: row?.id ?? data.id, configuration })
|
|
327
357
|
}}><fieldset disabled={busy}>{!row && field('id')}
|
|
328
358
|
<label htmlFor={`${prefix}-transport`}>{t('transport')}<select id={`${prefix}-transport`} value={transport} onChange={event => setTransport(event.target.value)}>
|
|
329
|
-
<option value="stdio">{t('stdio')}</option><option value="streamable-http">{t('http')}</option></select></label>
|
|
359
|
+
<option value="stdio">{t('stdio')}</option><option value="streamable-http">{t('http')}</option><option value="sse">{t('sse')}</option></select></label>
|
|
330
360
|
{transport === 'stdio' ? <>{field('command', true, false, initial.command)}{field('args', false, true, (initial.args ?? []).join('\n'))}{field('cwd', false, false, initial.cwd)}</> : field('url', true, false, initial.url)}
|
|
331
361
|
<p>{t('secret')}</p>
|
|
332
362
|
<div className="dsh-ext-actions"><button type="button" onClick={onCancel}>{t('cancel')}</button><button type="submit">{t(busy ? 'saving' : row ? 'saveChanges' : 'save')}</button></div>
|
|
@@ -350,7 +380,7 @@ function AddForm({ kind, t, busy, onSave, onCancel }) {
|
|
|
350
380
|
void onSave(args)
|
|
351
381
|
}}><fieldset disabled={busy}>{kind === 'skill' ? field('directory') : <>{field('id')}
|
|
352
382
|
<label htmlFor={`${prefix}-transport`}>{t('transport')}<select id={`${prefix}-transport`} value={transport} onChange={event => setTransport(event.target.value)}>
|
|
353
|
-
<option value="stdio">{t('stdio')}</option><option value="streamable-http">{t('http')}</option></select></label>
|
|
383
|
+
<option value="stdio">{t('stdio')}</option><option value="streamable-http">{t('http')}</option><option value="sse">{t('sse')}</option></select></label>
|
|
354
384
|
{transport === 'stdio' ? <>{field('command')}{field('args', false, true)}{field('cwd', false)}</> : field('url')}
|
|
355
385
|
<p>{t('secret')}</p>
|
|
356
386
|
</>}
|
package/src/client/locales.js
CHANGED
|
@@ -5,15 +5,17 @@ export const zh = {
|
|
|
5
5
|
skills: 'Skill 路径', mcp: 'MCP 服务器', refresh: '刷新', add: '添加路径', addMcp: '添加服务器', newSkill: '新建 Skill',
|
|
6
6
|
emptySkills: '还没有配置 Skill 路径', emptySkillsHint: '指定一个本地 skills 目录,目录下的各个子目录就是技能。',
|
|
7
7
|
emptyMcp: '还没有添加 MCP 服务器', emptyMcpHint: '通过本地命令或 HTTP 地址连接外部工具。',
|
|
8
|
-
id: '标识', directory: 'Skill 路径', transport: '配置类型', stdio: 'stdio', http: 'HTTP',
|
|
8
|
+
id: '标识', directory: 'Skill 路径', transport: '配置类型', stdio: 'stdio', http: 'Streamable HTTP', sse: 'SSE(旧版兼容)',
|
|
9
9
|
command: '命令', args: '参数(每行一个)', cwd: '工作目录(可选)', url: '服务器地址',
|
|
10
|
-
cancel: '取消', save: '添加', saveChanges: '保存修改', create: '创建', edit: '编辑', delete: '删除', saving: '保存中…', loading: '加载中…',
|
|
11
|
-
|
|
10
|
+
cancel: '取消', save: '添加', saveChanges: '保存修改', create: '创建', edit: '编辑', delete: '删除', confirmDelete: '确认删除', deleting: '删除中…', saving: '保存中…', loading: '加载中…',
|
|
11
|
+
deleteExtensionTitle: '移除 Skill 来源', deleteMcpTitle: '删除 MCP 服务器', deleteSkillTitle: '永久删除 Skill',
|
|
12
|
+
deleteExtensionConfirm: '将从当前配置中移除“{name}”,目录中的 Skill 文件会保留。', deleteMcpConfirm: '将删除 MCP 服务器“{name}”及其连接配置。',
|
|
13
|
+
deleteSkillConfirm: '将永久删除“{name}”及其目录内的资源文件。此操作无法撤销。',
|
|
12
14
|
enabled: '配置已启用', disabled: '已禁用', enable: '启用', disable: '禁用',
|
|
13
15
|
saved: '更改已保存。', failed: '操作失败,请检查输入、连接或服务端日志后重试。',
|
|
14
16
|
conflict: '配置已被修改,请刷新后重试。未提交的表单内容已保留。',
|
|
15
17
|
skillFootnote: '新路径默认禁用。启用后,Skill provider 会扫描该路径下的所有技能。配置变更会自动生效。',
|
|
16
|
-
mcpFootnote: '
|
|
18
|
+
mcpFootnote: '新服务器默认禁用。新服务推荐 Streamable HTTP;SSE 仅用于兼容旧版 /sse + /messages 服务。配置变更会自动生效。',
|
|
17
19
|
secret: '请勿填写密钥或带令牌的地址。此版本暂不支持认证参数。',
|
|
18
20
|
tools: '工具列表', status_available: '工具可用', status_waiting: '等待发现工具', status_disabled: '已禁用',
|
|
19
21
|
skillHint: '启用后由 Skill provider 扫描;每个配置项对应一个包含多个技能的目录。',
|
|
@@ -41,15 +43,17 @@ export const en = {
|
|
|
41
43
|
skills: 'Skill paths', mcp: 'MCP servers', refresh: 'Refresh', add: 'Add path', addMcp: 'Add server', newSkill: 'New Skill',
|
|
42
44
|
emptySkills: 'No Skill paths configured', emptySkillsHint: 'Choose a local skills directory whose children contain the individual skills.',
|
|
43
45
|
emptyMcp: 'No MCP servers yet', emptyMcpHint: 'Connect external tools with a local command or HTTP endpoint.',
|
|
44
|
-
id: 'Identifier', directory: 'Skill path', transport: 'Type', stdio: 'stdio', http: 'HTTP',
|
|
46
|
+
id: 'Identifier', directory: 'Skill path', transport: 'Type', stdio: 'stdio', http: 'Streamable HTTP', sse: 'SSE (legacy)',
|
|
45
47
|
command: 'Command', args: 'Arguments (one per line)', cwd: 'Working directory (optional)', url: 'Server URL',
|
|
46
|
-
cancel: 'Cancel', save: 'Add', saveChanges: 'Save changes', create: 'Create', edit: 'Edit', delete: 'Delete', saving: 'Saving…', loading: 'Loading…',
|
|
47
|
-
|
|
48
|
+
cancel: 'Cancel', save: 'Add', saveChanges: 'Save changes', create: 'Create', edit: 'Edit', delete: 'Delete', confirmDelete: 'Delete', deleting: 'Deleting…', saving: 'Saving…', loading: 'Loading…',
|
|
49
|
+
deleteExtensionTitle: 'Remove Skill source', deleteMcpTitle: 'Delete MCP server', deleteSkillTitle: 'Permanently delete Skill',
|
|
50
|
+
deleteExtensionConfirm: 'Remove “{name}” from this configuration. Skill files in the directory will be kept.', deleteMcpConfirm: 'Delete MCP server “{name}” and its connection configuration.',
|
|
51
|
+
deleteSkillConfirm: 'Permanently delete “{name}” and the resource files in its directory. This cannot be undone.',
|
|
48
52
|
enabled: 'Configured enabled', disabled: 'Disabled', enable: 'Enable', disable: 'Disable',
|
|
49
53
|
saved: 'Changes saved.', failed: 'Operation failed. Check input, connection or server logs and retry.',
|
|
50
54
|
conflict: 'Configuration changed. Refresh and retry; your draft has been retained.',
|
|
51
55
|
skillFootnote: 'New paths are disabled. Once enabled, the Skill provider scans every skill below them.',
|
|
52
|
-
mcpFootnote: 'New servers are disabled.
|
|
56
|
+
mcpFootnote: 'New servers are disabled. Prefer Streamable HTTP for new servers; SSE only supports legacy /sse + /messages endpoints.',
|
|
53
57
|
secret: 'Do not enter secrets or token-bearing URLs. Authentication fields are not supported yet.',
|
|
54
58
|
tools: 'Tools', status_available: 'Tools available', status_waiting: 'Waiting for tools', status_disabled: 'Disabled',
|
|
55
59
|
skillHint: 'The Skill provider scans enabled paths; every configured path contains multiple skills.',
|
package/src/client/styles.css
CHANGED
|
@@ -19,6 +19,8 @@
|
|
|
19
19
|
.dsh-ext input,.dsh-ext select,.dsh-ext textarea{box-sizing:border-box;min-width:0;width:100%;font:inherit;color:inherit;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l4);border-radius:8px;padding:10px}.dsh-ext textarea{resize:vertical}
|
|
20
20
|
.dsh-ext-actions{display:flex;justify-content:flex-end;gap:8px;grid-column:1/-1}.dsh-ext fieldset>p{grid-column:1/-1;margin:0}.dsh-ext-notice{padding:12px 16px;border:1px solid var(--dsw-alias-border-l4);border-radius:10px}.dsh-ext-footnote{padding:0 4px}
|
|
21
21
|
.dsh-ext-row-actions{display:flex;align-items:center;gap:7px;flex-wrap:wrap;justify-content:flex-end}.dsh-ext .dsh-ext-danger{color:#e98282}.dsh-ext-inline-form{width:100%;padding:14px 0 2px;border:0}.dsh-skill-edit-form{margin-top:12px;padding:14px;border-radius:10px;background:var(--dsw-alias-bg-layer-1)}
|
|
22
|
+
.dsh-confirm{box-sizing:border-box;width:min(430px,calc(100vw - 32px));padding:22px;border:1px solid var(--dsw-alias-border-l4);border-radius:16px;background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);box-shadow:0 20px 60px rgba(0,0,0,.28);font:inherit}.dsh-confirm::backdrop{background:rgba(10,12,16,.48);backdrop-filter:blur(3px)}
|
|
23
|
+
.dsh-confirm-icon{display:grid;place-items:center;width:34px;height:34px;margin-bottom:16px;border-radius:10px;background:color-mix(in srgb,#e05252 14%,transparent);color:#e98282;font-size:19px;font-weight:700}.dsh-confirm-copy h3{margin:0;font-size:16px}.dsh-confirm-copy p{margin:8px 0 0;color:var(--dsw-alias-label-tertiary);font-size:13px;line-height:1.65}.dsh-confirm-actions{display:flex;justify-content:flex-end;gap:8px;margin-top:22px}.dsh-confirm button{font:inherit;font-size:13px;cursor:pointer;border:1px solid var(--dsw-alias-border-l4);border-radius:9px;background:var(--dsw-alias-bg-layer-2);color:inherit;padding:8px 14px}.dsh-confirm button:hover{background:var(--dsw-alias-bg-layer-3)}.dsh-confirm button:disabled{opacity:.5;cursor:default}.dsh-confirm button:focus-visible{outline:2px solid var(--dsw-alias-brand-primary);outline-offset:3px}.dsh-confirm .dsh-confirm-delete{border-color:color-mix(in srgb,#e05252 45%,var(--dsw-alias-border-l4));background:#b83232;color:#fff}.dsh-confirm .dsh-confirm-delete:hover{background:#c63b3b}
|
|
22
24
|
.dsh-ext-heading-actions{display:flex;gap:8px;align-items:center}.dsh-visually-hidden{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
|
|
23
25
|
.dsh-skill-stats{display:grid;grid-template-columns:repeat(4,1fr);gap:10px;margin-top:20px}.dsh-skill-stats>div{display:flex;flex-direction:column;gap:4px;padding:14px 16px;border:1px solid var(--dsw-alias-border-l4);border-radius:12px;background:var(--dsw-alias-bg-layer-2)}.dsh-skill-stats strong{font-size:20px}.dsh-skill-stats span{font-size:12px;color:var(--dsw-alias-label-tertiary)}
|
|
24
26
|
.dsh-skill-search{margin-top:14px}.dsh-skill-search input{padding-left:13px}.dsh-skill-manager>.dsh-ext-form{margin-top:14px;border:1px solid var(--dsw-alias-border-l4);border-radius:14px;background:var(--dsw-alias-bg-layer-2)}
|
package/src/index.js
CHANGED
|
@@ -35,7 +35,7 @@ export async function apply(ctx, config) {
|
|
|
35
35
|
register('extensions_add_skill', 'Add an existing root containing skill directories. Added sources are disabled until explicitly enabled.', {
|
|
36
36
|
id: string, directory: string,
|
|
37
37
|
}, (args, exec) => store.addSkill(args.id, args.directory, exec.signal))
|
|
38
|
-
register('extensions_add_mcp', 'Add a disabled MCP server. configuration is JSON
|
|
38
|
+
register('extensions_add_mcp', 'Add a disabled MCP server. configuration is JSON for stdio, Streamable HTTP, or legacy SSE transport. Do not supply secrets in arguments.', {
|
|
39
39
|
id: string, configuration: string,
|
|
40
40
|
}, (args, exec) => {
|
|
41
41
|
const input = JSON.parse(args.configuration)
|
package/src/mcp-sse.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/** Legacy HTTP+SSE MCP client used only for servers that predate Streamable HTTP. */
|
|
2
|
+
import Schema from '@deepseek-ai/schemastery'
|
|
3
|
+
import { Client } from '@modelcontextprotocol/sdk/client/index.js'
|
|
4
|
+
import { SSEClientTransport } from '@modelcontextprotocol/sdk/client/sse.js'
|
|
5
|
+
import { CallToolResultSchema, ListToolsResultSchema, ToolListChangedNotificationSchema } from '@modelcontextprotocol/sdk/types.js'
|
|
6
|
+
import { createHash } from 'node:crypto'
|
|
7
|
+
|
|
8
|
+
export const name = 'mcp-sse-client'
|
|
9
|
+
export const inject = ['tools']
|
|
10
|
+
|
|
11
|
+
const serverNamePattern = /^[A-Za-z0-9_-]{1,32}$/
|
|
12
|
+
const invalidNameCharacters = /[^A-Za-z0-9_-]/g
|
|
13
|
+
const maxPublicNameLength = 64
|
|
14
|
+
|
|
15
|
+
export const Config = Schema.object({
|
|
16
|
+
transport: Schema.const('sse'),
|
|
17
|
+
serverName: Schema.string().required().pattern(serverNamePattern),
|
|
18
|
+
url: Schema.string().required(),
|
|
19
|
+
toolCallTimeoutMs: Schema.number().default(60_000),
|
|
20
|
+
failOnStartupError: Schema.boolean().default(false),
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
export async function apply(ctx, config) {
|
|
24
|
+
const client = new Client({ name: `dsh-sse-${config.serverName}`, version: '1.0.0' }, { capabilities: {} })
|
|
25
|
+
const transport = new SSEClientTransport(new URL(config.url))
|
|
26
|
+
let disposed = false
|
|
27
|
+
let registrations = new Map()
|
|
28
|
+
let synchronization = Promise.resolve()
|
|
29
|
+
|
|
30
|
+
const clearTools = () => {
|
|
31
|
+
for (const dispose of registrations.values()) dispose()
|
|
32
|
+
registrations = new Map()
|
|
33
|
+
}
|
|
34
|
+
ctx.effect(() => () => {
|
|
35
|
+
disposed = true
|
|
36
|
+
clearTools()
|
|
37
|
+
void client.close().catch(() => {})
|
|
38
|
+
}, 'mcp-sse-client.connection')
|
|
39
|
+
|
|
40
|
+
const synchronizeTools = async () => {
|
|
41
|
+
const definitions = new Map()
|
|
42
|
+
let cursor
|
|
43
|
+
do {
|
|
44
|
+
const response = await client.request({
|
|
45
|
+
method: 'tools/list',
|
|
46
|
+
...(cursor === undefined ? {} : { params: { cursor } }),
|
|
47
|
+
}, ListToolsResultSchema)
|
|
48
|
+
for (const tool of response.tools) {
|
|
49
|
+
const publicName = publicToolName(config.serverName, tool.name)
|
|
50
|
+
if (definitions.has(publicName)) throw new Error(`SSE MCP server listed duplicate tool: ${tool.name}`)
|
|
51
|
+
definitions.set(publicName, createTool(client, publicName, tool, config.toolCallTimeoutMs))
|
|
52
|
+
}
|
|
53
|
+
cursor = response.nextCursor
|
|
54
|
+
} while (cursor)
|
|
55
|
+
if (disposed) return
|
|
56
|
+
clearTools()
|
|
57
|
+
const next = new Map()
|
|
58
|
+
try {
|
|
59
|
+
for (const [publicName, definition] of definitions) next.set(publicName, ctx.tools.register(definition))
|
|
60
|
+
registrations = next
|
|
61
|
+
} catch (error) {
|
|
62
|
+
for (const dispose of next.values()) dispose()
|
|
63
|
+
throw error
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
client.setNotificationHandler(ToolListChangedNotificationSchema, () => {
|
|
68
|
+
synchronization = synchronization.then(synchronizeTools).catch(error => {
|
|
69
|
+
ctx.logger.warn(`mcp-sse-client(${config.serverName}): tool refresh failed: ${messageOf(error)}`)
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
await client.connect(transport)
|
|
75
|
+
await synchronizeTools()
|
|
76
|
+
} catch (error) {
|
|
77
|
+
clearTools()
|
|
78
|
+
await client.close().catch(() => {})
|
|
79
|
+
if (config.failOnStartupError) throw new Error(`mcp-sse-client(${config.serverName}): initial connection failed: ${messageOf(error)}`, { cause: error })
|
|
80
|
+
ctx.logger.warn(`mcp-sse-client(${config.serverName}): initial connection failed: ${messageOf(error)}`)
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function createTool(client, publicName, tool, timeout) {
|
|
85
|
+
return {
|
|
86
|
+
name: publicName,
|
|
87
|
+
description: tool.description ?? '',
|
|
88
|
+
parameters: tool.inputSchema,
|
|
89
|
+
output: {
|
|
90
|
+
schema: {
|
|
91
|
+
type: 'object',
|
|
92
|
+
properties: { content: { type: 'array', items: {} }, structuredContent: {} },
|
|
93
|
+
required: ['content'],
|
|
94
|
+
additionalProperties: true,
|
|
95
|
+
},
|
|
96
|
+
render: (_args, value) => [{ type: 'text', text: textContent(value.content) }],
|
|
97
|
+
},
|
|
98
|
+
async execute(args, exec) {
|
|
99
|
+
const result = await client.request({ method: 'tools/call', params: { name: tool.name, arguments: args } }, CallToolResultSchema, {
|
|
100
|
+
signal: exec.signal,
|
|
101
|
+
timeout,
|
|
102
|
+
})
|
|
103
|
+
if (result.isError) throw new Error(textContent(result.content) || `MCP tool ${tool.name} failed`)
|
|
104
|
+
return result
|
|
105
|
+
},
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function publicToolName(serverName, rawName) {
|
|
110
|
+
const joined = `mcp__${serverName}__${rawName}`
|
|
111
|
+
const normalized = joined.replace(invalidNameCharacters, '_')
|
|
112
|
+
if (normalized === joined && normalized.length <= maxPublicNameLength) return normalized
|
|
113
|
+
const hash = createHash('sha256').update(`${serverName}\0${rawName}`).digest('hex').slice(0, 12)
|
|
114
|
+
return `${normalized.slice(0, maxPublicNameLength - 13)}_${hash}`
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function textContent(content) {
|
|
118
|
+
const text = Array.isArray(content) ? content.filter(item => item?.type === 'text').map(item => item.text).join('\n') : ''
|
|
119
|
+
return text || JSON.stringify(content ?? [])
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const messageOf = error => error instanceof Error ? error.message : String(error)
|
package/src/store.js
CHANGED
|
@@ -4,11 +4,13 @@ import { basename, isAbsolute, resolve, win32 } from 'node:path'
|
|
|
4
4
|
import { randomUUID, createHash } from 'node:crypto'
|
|
5
5
|
import { Config as McpConfig } from '@deepseek-ai/dsh-mcp-client'
|
|
6
6
|
import { Config as SkillConfig } from '@deepseek-ai/dsh-skill-filesystem'
|
|
7
|
+
import { Config as SseMcpConfig } from './mcp-sse.js'
|
|
7
8
|
import { parse, stringify } from 'yaml'
|
|
8
9
|
|
|
9
10
|
const moduleNames = {
|
|
10
11
|
skill: '@deepseek-ai/dsh-skill-filesystem',
|
|
11
12
|
mcp: '@deepseek-ai/dsh-mcp-client',
|
|
13
|
+
mcpSse: '@civilization/deepseek-harness-skill-mcp/mcp-sse',
|
|
12
14
|
}
|
|
13
15
|
const managerModule = new URL('./index.js', import.meta.url).href
|
|
14
16
|
const resolvedModules = Object.fromEntries(Object.entries(moduleNames).map(([kind, name]) => [kind, import.meta.resolve(name)]))
|
|
@@ -61,6 +63,9 @@ function parsePatch(value) {
|
|
|
61
63
|
} else if (isModule(row.name, 'mcp')) {
|
|
62
64
|
McpConfig(row.config)
|
|
63
65
|
if (row.config.serverName !== row.id) throw new Error('MCP namespace must match its id.')
|
|
66
|
+
} else if (isModule(row.name, 'mcpSse')) {
|
|
67
|
+
SseMcpConfig(row.config)
|
|
68
|
+
if (row.config.serverName !== row.id) throw new Error('MCP namespace must match its id.')
|
|
64
69
|
} else throw new Error('Unknown managed plugin module.')
|
|
65
70
|
managed.push(row)
|
|
66
71
|
}
|
|
@@ -183,7 +188,8 @@ export class ExtensionStore {
|
|
|
183
188
|
async addMcp(id, input, signal, expectedRevision) {
|
|
184
189
|
validateId(id)
|
|
185
190
|
const config = this.normalizeMcp(id, input)
|
|
186
|
-
|
|
191
|
+
const name = config.transport === 'sse' ? moduleNames.mcpSse : moduleNames.mcp
|
|
192
|
+
return this.add({ id, name, disabled: true, config }, signal, expectedRevision)
|
|
187
193
|
}
|
|
188
194
|
|
|
189
195
|
normalizeMcp(id, input) {
|
|
@@ -194,8 +200,10 @@ export class ExtensionStore {
|
|
|
194
200
|
command: typeof input.command === 'string' ? normalizeWindowsPath(input.command) : input.command,
|
|
195
201
|
cwd: typeof input.cwd === 'string' && input.cwd ? normalizeWindowsPath(input.cwd) : input.cwd,
|
|
196
202
|
} : input
|
|
197
|
-
const config =
|
|
198
|
-
|
|
203
|
+
const config = input.transport === 'sse'
|
|
204
|
+
? SseMcpConfig({ ...normalized, serverName: id, failOnStartupError: false })
|
|
205
|
+
: McpConfig({ ...normalized, serverName: id, failOnStartupError: false })
|
|
206
|
+
if (config.transport === 'streamable-http' || config.transport === 'sse') {
|
|
199
207
|
const url = new URL(config.url)
|
|
200
208
|
if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash) throw new Error('Use an HTTP(S) endpoint without credentials, query or fragment.')
|
|
201
209
|
} else if (!config.command.trim()) throw new Error('MCP command cannot be empty.')
|
|
@@ -235,6 +243,7 @@ export class ExtensionStore {
|
|
|
235
243
|
return this.update(rows => {
|
|
236
244
|
const row = rows.find(row => row.id === id && row.config.serverName)
|
|
237
245
|
if (!row) throw new Error('Unknown MCP server.')
|
|
246
|
+
row.name = config.transport === 'sse' ? moduleNames.mcpSse : moduleNames.mcp
|
|
238
247
|
row.config = config
|
|
239
248
|
}, signal, expectedRevision)
|
|
240
249
|
}
|
package/src/web.js
CHANGED
|
@@ -43,7 +43,7 @@ export async function describeExtensions(store, ctx) {
|
|
|
43
43
|
kind: row.config.serverName ? 'mcp' : 'skill',
|
|
44
44
|
enabled: !row.disabled,
|
|
45
45
|
location: row.config.transport === 'stdio' ? row.config.command
|
|
46
|
-
: row.config.transport
|
|
46
|
+
: ['streamable-http', 'sse'].includes(row.config.transport) ? new URL(row.config.url).origin
|
|
47
47
|
: row.config.customSkillDirs[0],
|
|
48
48
|
transport: row.config.transport ?? '',
|
|
49
49
|
configuration: row.config.serverName ? (row.config.transport === 'stdio'
|