@civilization/deepseek-harness-skill-mcp 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -1
- package/lib/client.js +185 -6
- package/package.json +1 -1
- package/src/catalog.js +154 -0
- package/src/client/index.jsx +149 -4
- package/src/client/locales.js +30 -0
- package/src/client/styles.css +36 -0
- package/src/index.js +57 -4
- package/src/store.js +62 -1
- package/src/web.js +26 -6
package/README.md
CHANGED
|
@@ -69,6 +69,38 @@ Skill 页面支持:
|
|
|
69
69
|
|
|
70
70
|
新路径默认禁用。启用后,Harness 的 Skill provider 扫描该路径中的技能。页面在前台时每 3 秒刷新一次。当前 Harness Web 会按会话缓存 `/` Skill 候选,但未把 Host 的 `skills/change` 转发给浏览器;本插件在管理操作成功或用户点击“刷新”后触发客户端目录缓存失效,使下一次打开 `/` 菜单重新读取 Skill 列表。直接在文件系统外部修改 Skill 时,在管理页点击一次“刷新”即可同步菜单。
|
|
71
71
|
|
|
72
|
+
## 工具管理 (Skill Catalog)
|
|
73
|
+
|
|
74
|
+
通过新增的 `skill-catalog` 配置项,插件可管理当前 Harness 运行时中各个已安装插件所注册的工具(如 `dsh-autotask` 的自动化任务工具、`dsh-drawio` 的架构绘图工具、`dsh-playwright` 的浏览器工具、MCP 工具等),支持自由选择性启用或禁用。
|
|
75
|
+
|
|
76
|
+
### 1. 配置项 `skill-catalog`
|
|
77
|
+
支持在 `cordis.patch.yml` 中或设置界面配置:
|
|
78
|
+
|
|
79
|
+
```yaml
|
|
80
|
+
- insert:
|
|
81
|
+
- id: dsh-skill-mcp
|
|
82
|
+
name: "@civilization/deepseek-harness-skill-mcp"
|
|
83
|
+
config:
|
|
84
|
+
skill-catalog:
|
|
85
|
+
disabledTools:
|
|
86
|
+
- session_create_autotask
|
|
87
|
+
- drawio_render
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### 2. 接管与安全拦截机制
|
|
91
|
+
- **自动归类**:自动扫描运行时注册的所有工具,根据工具命名规则智能归属到所属插件(如 `session_*_autotask` 归属于 `dsh-autotask`,`drawio_*` 归属于 `dsh-drawio`,`browser_*` 归属于 `dsh-playwright`,`mcp__*` 归属于对应 MCP 服务)。
|
|
92
|
+
- **单调守卫拦截 (`ctx.tools.guard`)**:当工具被配置为禁用时,Cordis 单调守卫将在工具调用前拦截并返回友好提示(`【Skill Catalog 接管】工具 "..." 当前已被禁用`),杜绝 Agent 越权或误调用。
|
|
93
|
+
- **瀑布流预检查 (`tools/pre-execute`)**:在调度层提前做出 `deny` 决策。
|
|
94
|
+
- **核心工具保护**:PTC 核心传输通道与内部 catalog 控制工具自动设为受保护状态,防止误禁用导致环境不可用。
|
|
95
|
+
|
|
96
|
+
### 3. Web 界面操作
|
|
97
|
+
设置中心的“**工具接管**”页面提供:
|
|
98
|
+
- 总工具数、已启用、已禁用和插件/服务统计面板;
|
|
99
|
+
- 实时搜索工具名称、描述与插件前缀;
|
|
100
|
+
- 按插件分组筛选与分组折叠卡片;
|
|
101
|
+
- 每个工具独立的现代化开关(Switch Toggle);
|
|
102
|
+
- 插件级别的“全选启用”与“全部禁用”快捷操作。
|
|
103
|
+
|
|
72
104
|
## MCP 管理
|
|
73
105
|
|
|
74
106
|
支持三种 MCP 配置类型:
|
|
@@ -116,7 +148,7 @@ MCP 对端离线、命令启动失败或首次工具发现失败不会阻止 DSH
|
|
|
116
148
|
|
|
117
149
|
## Agent 工具与 Skill
|
|
118
150
|
|
|
119
|
-
插件向 DSH 注册了 `extension-management` Skill 以及
|
|
151
|
+
插件向 DSH 注册了 `extension-management` Skill 以及 9 个模型专属管理工具:
|
|
120
152
|
|
|
121
153
|
- **`extension-management` Skill**:引导模型掌握受管 Skill 路径与 stdio / streamable-http / sse 模式 MCP 服务器的标准配置结构与安全规约。
|
|
122
154
|
|
|
@@ -130,6 +162,8 @@ MCP 对端离线、命令启动失败或首次工具发现失败不会阻止 DSH
|
|
|
130
162
|
| `extensions_update_mcp` | 替换 MCP 连接配置并保留 id 与启用状态 |
|
|
131
163
|
| `extensions_remove` | 移除 Skill 来源或 MCP 配置;不会删除 Skill 来源目录 |
|
|
132
164
|
| `extensions_inspect` | 查看当前 Agent 实际可见的 Skill 与 MCP 工具 |
|
|
165
|
+
| `extensions_catalog_list` | 列出系统中所有发现的工具、插件归类与启停状态 |
|
|
166
|
+
| `extensions_catalog_set` | 启用或禁用特定工具(受 skill-catalog 接管) |
|
|
133
167
|
|
|
134
168
|
可以直接告诉 Agent:
|
|
135
169
|
|
package/lib/client.js
CHANGED
|
@@ -21,6 +21,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
// src/client/index.jsx
|
|
22
22
|
var index_exports = {};
|
|
23
23
|
__export(index_exports, {
|
|
24
|
+
CatalogSection: () => CatalogSection,
|
|
24
25
|
McpSection: () => McpSection,
|
|
25
26
|
SkillSection: () => SkillSection,
|
|
26
27
|
apply: () => apply,
|
|
@@ -200,7 +201,22 @@ var zh = {
|
|
|
200
201
|
"issue_missing-description": "\u7F3A\u5C11 description",
|
|
201
202
|
"issue_invalid-invocation": "\u8C03\u7528\u6743\u9650\u5B57\u6BB5\u4E0D\u662F\u6709\u6548\u5E03\u5C14\u503C",
|
|
202
203
|
issue_unreadable: "\u6587\u4EF6\u65E0\u6CD5\u8BFB\u53D6",
|
|
203
|
-
"issue_source-unreadable": "Skill \u8DEF\u5F84\u65E0\u6CD5\u8BFB\u53D6"
|
|
204
|
+
"issue_source-unreadable": "Skill \u8DEF\u5F84\u65E0\u6CD5\u8BFB\u53D6",
|
|
205
|
+
navCatalog: "\u5DE5\u5177\u7BA1\u7406",
|
|
206
|
+
catalogIntro: "\u7BA1\u7406\u7CFB\u7EDF\u4E2D\u5DF2\u5B89\u88C5\u5404\u63D2\u4EF6\u4E0E\u670D\u52A1\u7684\u5DE5\u5177\uFF0C\u53EF\u81EA\u7531\u9009\u62E9\u542F\u7528\u6216\u7981\u7528\u3002\u88AB\u7981\u7528\u7684\u5DE5\u5177\u5C06\u88AB\u5B89\u5168\u62E6\u622A\u3002",
|
|
207
|
+
catalogSearchPlaceholder: "\u641C\u7D22\u5DE5\u5177\u540D\u3001\u63CF\u8FF0\u6216\u6240\u5C5E\u63D2\u4EF6\u2026",
|
|
208
|
+
stat_catalogTotal: "\u603B\u5DE5\u5177\u6570",
|
|
209
|
+
stat_catalogEnabled: "\u5DF2\u542F\u7528",
|
|
210
|
+
stat_catalogDisabled: "\u5DF2\u7981\u7528",
|
|
211
|
+
stat_catalogPlugins: "\u63D2\u4EF6/\u670D\u52A1",
|
|
212
|
+
enableAll: "\u5168\u9009\u542F\u7528",
|
|
213
|
+
disableAll: "\u5168\u90E8\u7981\u7528",
|
|
214
|
+
filterPlugin: "\u63D2\u4EF6\u5206\u7EC4",
|
|
215
|
+
filterAllPlugins: "\u5168\u90E8\u63D2\u4EF6",
|
|
216
|
+
noCatalogResults: "\u672A\u627E\u5230\u5339\u914D\u7684\u5DE5\u5177",
|
|
217
|
+
noCatalogResultsHint: "\u8BF7\u5C1D\u8BD5\u4FEE\u6539\u641C\u7D22\u8BCD\u6216\u9009\u62E9\u5176\u4ED6\u63D2\u4EF6\u5206\u7EC4\u3002",
|
|
218
|
+
toolProtected: "\u6838\u5FC3\u4FDD\u7559",
|
|
219
|
+
toolTakeoverHint: "\u6B64\u5DE5\u5177\u53D7 skill-catalog \u7EDF\u4E00\u7BA1\u7406\u3002"
|
|
204
220
|
};
|
|
205
221
|
var en = {
|
|
206
222
|
navSkill: "Skills",
|
|
@@ -311,11 +327,26 @@ var en = {
|
|
|
311
327
|
"issue_missing-description": "Missing description",
|
|
312
328
|
"issue_invalid-invocation": "Invocation field is not a valid boolean",
|
|
313
329
|
issue_unreadable: "File cannot be read",
|
|
314
|
-
"issue_source-unreadable": "Skill path cannot be read"
|
|
330
|
+
"issue_source-unreadable": "Skill path cannot be read",
|
|
331
|
+
navCatalog: "Tools",
|
|
332
|
+
catalogIntro: "Manage tools from installed plugins and services. Enable or disable them selectively with safety guards.",
|
|
333
|
+
catalogSearchPlaceholder: "Search tool name, description or plugin\u2026",
|
|
334
|
+
stat_catalogTotal: "Total Tools",
|
|
335
|
+
stat_catalogEnabled: "Enabled",
|
|
336
|
+
stat_catalogDisabled: "Disabled",
|
|
337
|
+
stat_catalogPlugins: "Plugins/Services",
|
|
338
|
+
enableAll: "Enable All",
|
|
339
|
+
disableAll: "Disable All",
|
|
340
|
+
filterPlugin: "Plugin Group",
|
|
341
|
+
filterAllPlugins: "All Plugins",
|
|
342
|
+
noCatalogResults: "No matching tools found",
|
|
343
|
+
noCatalogResultsHint: "Try adjusting your search terms or filter.",
|
|
344
|
+
toolProtected: "Protected",
|
|
345
|
+
toolTakeoverHint: "This tool is managed under skill-catalog."
|
|
315
346
|
};
|
|
316
347
|
|
|
317
348
|
// src/client/styles.css
|
|
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";
|
|
349
|
+
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\n/* Skill Catalog Takeover Styles */\n.dsh-catalog-section{margin-top:14px}\n.dsh-catalog-header-actions{display:flex;align-items:center;gap:8px}\n.dsh-catalog-group-card{margin-top:16px;border:1px solid var(--dsw-alias-border-l4);border-radius:14px;background:var(--dsw-alias-bg-layer-2);overflow:hidden}\n.dsh-catalog-group-header{display:flex;justify-content:space-between;align-items:center;padding:14px 18px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-3) 50%,transparent);border-bottom:1px solid var(--dsw-alias-border-l4);cursor:pointer;user-select:none}\n.dsh-catalog-group-header::-webkit-details-marker{display:none}\n.dsh-catalog-group-header::marker{content:""}\n.dsh-catalog-group-card:not([open]) .dsh-catalog-group-header{border-bottom:0}\n.dsh-catalog-group-title{display:flex;align-items:center;gap:10px}\n.dsh-catalog-group-title::before{content:"\u25B6";display:inline-block;font-size:11px;color:var(--dsw-alias-label-tertiary);transition:transform .2s ease}\n.dsh-catalog-group-card[open] .dsh-catalog-group-title::before{transform:rotate(90deg)}\n.dsh-catalog-group-title h4{margin:0;font-size:14px;font-weight:600}\n.dsh-catalog-group-actions{display:flex;align-items:center;gap:6px}\n.dsh-catalog-group-actions button{padding:4px 10px;font-size:12px;border-radius:7px}\n.dsh-catalog-tool-item{display:flex;justify-content:space-between;align-items:center;gap:16px;padding:14px 18px;border-bottom:1px solid var(--dsw-alias-border-l4);transition:background .15s ease}\n.dsh-catalog-tool-item:last-child{border-bottom:0}\n.dsh-catalog-tool-item:hover{background:color-mix(in srgb,var(--dsw-alias-bg-layer-3) 40%,transparent)}\n.dsh-catalog-tool-info{display:flex;flex-direction:column;gap:5px;min-width:0;flex:1}\n.dsh-catalog-tool-header{display:flex;align-items:center;gap:8px;flex-wrap:wrap}\n.dsh-catalog-tool-header code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:13px;font-weight:600;color:var(--dsw-alias-brand-primary)}\n.dsh-catalog-tool-info p{margin:0;font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.5}\n.dsh-catalog-switch-wrap{display:flex;align-items:center;gap:10px;flex-shrink:0}\n.dsh-catalog-switch-label{font-size:12px;font-weight:500}\n.dsh-catalog-switch{position:relative;display:inline-block;width:40px;height:22px;cursor:pointer}\n.dsh-catalog-switch input{opacity:0;width:0;height:0;position:absolute}\n.dsh-catalog-slider{position:absolute;cursor:pointer;inset:0;background:var(--dsw-alias-bg-layer-4,#3b3b3b);border:1px solid var(--dsw-alias-border-l4);border-radius:22px;transition:.2s}\n.dsh-catalog-slider:before{position:absolute;content:"";height:16px;width:16px;left:2px;bottom:2px;background:#fff;border-radius:50%;transition:.2s}\n.dsh-catalog-switch input:checked + .dsh-catalog-slider{background:var(--dsw-alias-brand-primary,#3182ce);border-color:var(--dsw-alias-brand-primary,#3182ce)}\n.dsh-catalog-switch input:checked + .dsh-catalog-slider:before{transform:translateX(18px)}\n.dsh-catalog-switch input:disabled + .dsh-catalog-slider{opacity:.4;cursor:not-allowed}\n.dsh-catalog-tool-details{margin-top:4px;width:100%}\n.dsh-catalog-tool-details summary{width:max-content;cursor:pointer;font-size:12px;color:var(--dsw-alias-label-secondary);user-select:none;transition:color .15s}\n.dsh-catalog-tool-details summary:hover{color:var(--dsw-alias-label-primary)}\n.dsh-catalog-tool-details p{margin:6px 0 2px;font-size:12px;color:var(--dsw-alias-label-tertiary);line-height:1.55;background:var(--dsw-alias-bg-layer-1);padding:8px 12px;border-radius:8px;border:1px solid var(--dsw-alias-border-l4)}\n\n';
|
|
319
350
|
|
|
320
351
|
// src/client/index.jsx
|
|
321
352
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -353,13 +384,14 @@ function apply(ctx) {
|
|
|
353
384
|
const section = (id, kind) => ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
354
385
|
name: "settings.section",
|
|
355
386
|
id,
|
|
356
|
-
order: kind === "skill" ? 16 : 17,
|
|
357
|
-
label: () => t(kind === "skill" ? "navSkill" : "navMcp"),
|
|
387
|
+
order: kind === "skill" ? 16 : kind === "mcp" ? 17 : 18,
|
|
388
|
+
label: () => t(kind === "skill" ? "navSkill" : kind === "mcp" ? "navMcp" : "navCatalog"),
|
|
358
389
|
locale: namespace,
|
|
359
390
|
inject: () => face
|
|
360
|
-
}, kind === "skill" ? SkillSection : McpSection));
|
|
391
|
+
}, kind === "skill" ? SkillSection : kind === "mcp" ? McpSection : CatalogSection));
|
|
361
392
|
section("skill-manager", "skill");
|
|
362
393
|
section("mcp-manager", "mcp");
|
|
394
|
+
section("skill-catalog", "catalog");
|
|
363
395
|
}
|
|
364
396
|
function SkillSection({ t, useManager, request }) {
|
|
365
397
|
const state = useManager((value) => value);
|
|
@@ -1002,5 +1034,152 @@ function AddForm({ kind, t, busy, onSave, onCancel }) {
|
|
|
1002
1034
|
] })
|
|
1003
1035
|
] }) });
|
|
1004
1036
|
}
|
|
1037
|
+
function CatalogSection({ t, useManager, request }) {
|
|
1038
|
+
const state = useManager((value) => value);
|
|
1039
|
+
const [query, setQuery] = (0, import_react.useState)("");
|
|
1040
|
+
const [pluginFilter, setPluginFilter] = (0, import_react.useState)("all");
|
|
1041
|
+
const busy = state.loading || state.saving;
|
|
1042
|
+
const catalog = state.catalog ?? { tools: [], groups: [], summary: { total: 0, enabled: 0, disabled: 0, pluginCount: 0 } };
|
|
1043
|
+
const groups = catalog.groups ?? [];
|
|
1044
|
+
const summary = catalog.summary ?? { total: 0, enabled: 0, disabled: 0, pluginCount: 0 };
|
|
1045
|
+
useLiveRefresh(request);
|
|
1046
|
+
const visibleGroups = groups.filter((g) => pluginFilter === "all" || g.id === pluginFilter).map((g) => {
|
|
1047
|
+
const filteredTools = g.tools.filter((tool) => {
|
|
1048
|
+
const text = `${tool.name} ${tool.description} ${g.name}`.toLowerCase();
|
|
1049
|
+
return text.includes(query.toLowerCase());
|
|
1050
|
+
});
|
|
1051
|
+
return { ...g, tools: filteredTools };
|
|
1052
|
+
}).filter((g) => g.tools.length > 0);
|
|
1053
|
+
const handleToggleTool = async (toolName, currentEnabled) => {
|
|
1054
|
+
await request("catalog-set-tool", { tool: toolName, enabled: !currentEnabled });
|
|
1055
|
+
};
|
|
1056
|
+
const handleToggleGroup = async (groupTools, targetEnabled) => {
|
|
1057
|
+
const names = groupTools.filter((t2) => t2.canToggle).map((t2) => t2.name);
|
|
1058
|
+
if (names.length === 0) return;
|
|
1059
|
+
await request("catalog-set-group", { tools: names, enabled: targetEnabled });
|
|
1060
|
+
};
|
|
1061
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("section", { className: "dsh-ext dsh-catalog-section", children: [
|
|
1062
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("header", { className: "dsh-ext-heading", children: [
|
|
1063
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
1064
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h2", { children: t("navCatalog") }),
|
|
1065
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: t("catalogIntro") })
|
|
1066
|
+
] }),
|
|
1067
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-ext-heading-actions", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy, onClick: () => request("list"), children: t("refresh") }) })
|
|
1068
|
+
] }),
|
|
1069
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(PageState, { state, t }),
|
|
1070
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-skill-stats", children: [
|
|
1071
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
1072
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: summary.total }),
|
|
1073
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("stat_catalogTotal") })
|
|
1074
|
+
] }),
|
|
1075
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
1076
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { color: "var(--dsw-alias-brand-primary, #3182ce)" }, children: summary.enabled }),
|
|
1077
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("stat_catalogEnabled") })
|
|
1078
|
+
] }),
|
|
1079
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
1080
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { style: { color: summary.disabled > 0 ? "#e05252" : "inherit" }, children: summary.disabled }),
|
|
1081
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("stat_catalogDisabled") })
|
|
1082
|
+
] }),
|
|
1083
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
|
|
1084
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: summary.pluginCount }),
|
|
1085
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: t("stat_catalogPlugins") })
|
|
1086
|
+
] })
|
|
1087
|
+
] }),
|
|
1088
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "dsh-skill-search", children: [
|
|
1089
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-visually-hidden", children: t("search") }),
|
|
1090
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1091
|
+
"input",
|
|
1092
|
+
{
|
|
1093
|
+
type: "search",
|
|
1094
|
+
value: query,
|
|
1095
|
+
onChange: (event) => setQuery(event.target.value),
|
|
1096
|
+
placeholder: t("catalogSearchPlaceholder")
|
|
1097
|
+
}
|
|
1098
|
+
)
|
|
1099
|
+
] }),
|
|
1100
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-skill-filters", style: { gridTemplateColumns: "1fr" }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { children: [
|
|
1101
|
+
t("filterPlugin"),
|
|
1102
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("select", { value: pluginFilter, onChange: (event) => setPluginFilter(event.target.value), children: [
|
|
1103
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("option", { value: "all", children: t("filterAllPlugins") }),
|
|
1104
|
+
groups.map((g) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("option", { value: g.id, children: [
|
|
1105
|
+
g.name,
|
|
1106
|
+
" (",
|
|
1107
|
+
g.total,
|
|
1108
|
+
")"
|
|
1109
|
+
] }, g.id))
|
|
1110
|
+
] })
|
|
1111
|
+
] }) }),
|
|
1112
|
+
visibleGroups.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ext-card dsh-ext-empty", children: [
|
|
1113
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("strong", { children: t("noCatalogResults") }),
|
|
1114
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: t("noCatalogResultsHint") })
|
|
1115
|
+
] }) : visibleGroups.map((group) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "dsh-catalog-group-card", children: [
|
|
1116
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("summary", { className: "dsh-catalog-group-header", children: [
|
|
1117
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-catalog-group-title", children: [
|
|
1118
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("h4", { children: group.name }),
|
|
1119
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsh-ext-count", children: [
|
|
1120
|
+
group.enabledCount,
|
|
1121
|
+
" / ",
|
|
1122
|
+
group.total,
|
|
1123
|
+
" \u53EF\u7528"
|
|
1124
|
+
] })
|
|
1125
|
+
] }),
|
|
1126
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-catalog-group-actions", onClick: (event) => event.stopPropagation(), children: [
|
|
1127
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1128
|
+
"button",
|
|
1129
|
+
{
|
|
1130
|
+
type: "button",
|
|
1131
|
+
disabled: busy,
|
|
1132
|
+
onClick: (event) => {
|
|
1133
|
+
event.stopPropagation();
|
|
1134
|
+
handleToggleGroup(group.tools, true);
|
|
1135
|
+
},
|
|
1136
|
+
children: t("enableAll")
|
|
1137
|
+
}
|
|
1138
|
+
),
|
|
1139
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1140
|
+
"button",
|
|
1141
|
+
{
|
|
1142
|
+
type: "button",
|
|
1143
|
+
disabled: busy,
|
|
1144
|
+
onClick: (event) => {
|
|
1145
|
+
event.stopPropagation();
|
|
1146
|
+
handleToggleGroup(group.tools, false);
|
|
1147
|
+
},
|
|
1148
|
+
children: t("disableAll")
|
|
1149
|
+
}
|
|
1150
|
+
)
|
|
1151
|
+
] })
|
|
1152
|
+
] }),
|
|
1153
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-catalog-tool-list", children: group.tools.map((tool) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-catalog-tool-item", children: [
|
|
1154
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-catalog-tool-info", children: [
|
|
1155
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-catalog-tool-header", children: [
|
|
1156
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: tool.name }),
|
|
1157
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `dsh-skill-badge ${tool.enabled ? "dsh-skill-badge-group" : "dsh-skill-badge-invalid"}`, children: tool.enabled ? t("enabled") : t("disabled") }),
|
|
1158
|
+
!tool.canToggle && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-skill-badge", children: t("toolProtected") })
|
|
1159
|
+
] }),
|
|
1160
|
+
tool.description && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "dsh-catalog-tool-details", children: [
|
|
1161
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("summary", { children: t("details") }),
|
|
1162
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: tool.description })
|
|
1163
|
+
] })
|
|
1164
|
+
] }),
|
|
1165
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-catalog-switch-wrap", children: [
|
|
1166
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-catalog-switch-label", style: { color: tool.enabled ? "var(--dsw-alias-label-primary)" : "var(--dsw-alias-label-tertiary)" }, children: t(tool.enabled ? "enable" : "disable") }),
|
|
1167
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { className: "dsh-catalog-switch", children: [
|
|
1168
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1169
|
+
"input",
|
|
1170
|
+
{
|
|
1171
|
+
type: "checkbox",
|
|
1172
|
+
checked: tool.enabled,
|
|
1173
|
+
disabled: busy || !tool.canToggle,
|
|
1174
|
+
onChange: () => handleToggleTool(tool.name, tool.enabled)
|
|
1175
|
+
}
|
|
1176
|
+
),
|
|
1177
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-catalog-slider" })
|
|
1178
|
+
] })
|
|
1179
|
+
] })
|
|
1180
|
+
] }, tool.name)) })
|
|
1181
|
+
] }, group.id))
|
|
1182
|
+
] });
|
|
1183
|
+
}
|
|
1005
1184
|
|
|
1006
1185
|
return module.exports;}});
|
package/package.json
CHANGED
package/src/catalog.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/** Plugin tool discovery, classification, and execution takeover management. */
|
|
2
|
+
|
|
3
|
+
/** Builtin core tools shipped with DSH runtime. */
|
|
4
|
+
const builtinToolNames = new Set([
|
|
5
|
+
'read_file', 'write_file', 'edit_file', 'apply_diff',
|
|
6
|
+
'run_command', 'bash', 'exec',
|
|
7
|
+
'grep_search', 'file_search', 'find_by_name', 'list_dir', 'view_file',
|
|
8
|
+
'schedule', 'manage_task',
|
|
9
|
+
'ask_question', 'generate_image', 'read_url_content', 'browser_subagent',
|
|
10
|
+
'run_code',
|
|
11
|
+
])
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Infer the plugin or category group for a given tool name.
|
|
15
|
+
* @param {string} toolName
|
|
16
|
+
* @returns {{ id: string, name: string, isPlugin: boolean }}
|
|
17
|
+
*/
|
|
18
|
+
export function classifyTool(toolName) {
|
|
19
|
+
if (toolName.startsWith('mcp__')) {
|
|
20
|
+
const parts = toolName.split('__')
|
|
21
|
+
const serverName = parts[1] || 'unknown'
|
|
22
|
+
return {
|
|
23
|
+
id: `mcp-${serverName}`,
|
|
24
|
+
name: `MCP 服务: ${serverName}`,
|
|
25
|
+
isPlugin: true,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (toolName.startsWith('session_') && toolName.includes('autotask')) {
|
|
30
|
+
return {
|
|
31
|
+
id: 'dsh-autotask',
|
|
32
|
+
name: '自动任务巡检 (dsh-autotask)',
|
|
33
|
+
isPlugin: true,
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (toolName.startsWith('drawio_') || toolName.startsWith('drawio-')) {
|
|
38
|
+
return {
|
|
39
|
+
id: 'dsh-drawio',
|
|
40
|
+
name: 'Draw.io 架构绘图 (dsh-drawio)',
|
|
41
|
+
isPlugin: true,
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (toolName.startsWith('browser_') || toolName.startsWith('playwright_')) {
|
|
46
|
+
return {
|
|
47
|
+
id: 'dsh-playwright',
|
|
48
|
+
name: '浏览器自动化 (dsh-playwright)',
|
|
49
|
+
isPlugin: true,
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (toolName.startsWith('workspace_')) {
|
|
54
|
+
return {
|
|
55
|
+
id: 'dsh-workspace-files',
|
|
56
|
+
name: '工作区文件管理 (dsh-workspace-files)',
|
|
57
|
+
isPlugin: true,
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (toolName.startsWith('process_')) {
|
|
62
|
+
return {
|
|
63
|
+
id: 'dsh-process-manager',
|
|
64
|
+
name: '系统进程管理 (dsh-process-manager)',
|
|
65
|
+
isPlugin: true,
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (toolName.startsWith('extensions_')) {
|
|
70
|
+
return {
|
|
71
|
+
id: 'dsh-skill-mcp',
|
|
72
|
+
name: '扩展与工具管理 (dsh-skill-mcp)',
|
|
73
|
+
isPlugin: true,
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (builtinToolNames.has(toolName)) {
|
|
78
|
+
return {
|
|
79
|
+
id: 'builtin',
|
|
80
|
+
name: '系统核心工具 (Builtin)',
|
|
81
|
+
isPlugin: false,
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Fallback for custom user plugins
|
|
86
|
+
const prefix = toolName.split('_')[0]
|
|
87
|
+
return {
|
|
88
|
+
id: prefix ? `plugin-${prefix}` : 'other-plugins',
|
|
89
|
+
name: prefix ? `扩展插件 (${prefix})` : '其他插件工具',
|
|
90
|
+
isPlugin: true,
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Describe all registered tools and aggregate them by category/plugin.
|
|
96
|
+
* @param {import('@deepseek-ai/cordis').Context} ctx
|
|
97
|
+
* @param {{ disabledTools?: string[] }} catalogConfig
|
|
98
|
+
*/
|
|
99
|
+
export function buildToolCatalog(ctx, catalogConfig = {}) {
|
|
100
|
+
const disabledSet = new Set(catalogConfig.disabledTools || [])
|
|
101
|
+
const schemas = ctx.tools ? ctx.tools.schemas() : []
|
|
102
|
+
|
|
103
|
+
const tools = schemas.map(schema => {
|
|
104
|
+
const classification = classifyTool(schema.name)
|
|
105
|
+
const enabled = !disabledSet.has(schema.name)
|
|
106
|
+
return {
|
|
107
|
+
name: schema.name,
|
|
108
|
+
description: schema.description || '',
|
|
109
|
+
parameters: schema.parameters || {},
|
|
110
|
+
groupId: classification.id,
|
|
111
|
+
groupName: classification.name,
|
|
112
|
+
isPlugin: classification.isPlugin,
|
|
113
|
+
enabled,
|
|
114
|
+
canToggle: schema.name !== 'run_code' && !schema.name.startsWith('extensions_catalog_'),
|
|
115
|
+
}
|
|
116
|
+
})
|
|
117
|
+
|
|
118
|
+
// Group by category
|
|
119
|
+
const groupsMap = new Map()
|
|
120
|
+
for (const tool of tools) {
|
|
121
|
+
if (!groupsMap.has(tool.groupId)) {
|
|
122
|
+
groupsMap.set(tool.groupId, {
|
|
123
|
+
id: tool.groupId,
|
|
124
|
+
name: tool.groupName,
|
|
125
|
+
isPlugin: tool.isPlugin,
|
|
126
|
+
tools: [],
|
|
127
|
+
total: 0,
|
|
128
|
+
enabledCount: 0,
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
const group = groupsMap.get(tool.groupId)
|
|
132
|
+
group.tools.push(tool)
|
|
133
|
+
group.total++
|
|
134
|
+
if (tool.enabled) group.enabledCount++
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const groups = Array.from(groupsMap.values()).sort((a, b) => {
|
|
138
|
+
if (a.isPlugin && !b.isPlugin) return -1
|
|
139
|
+
if (!a.isPlugin && b.isPlugin) return 1
|
|
140
|
+
return a.name.localeCompare(b.name)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
tools,
|
|
145
|
+
groups,
|
|
146
|
+
disabledTools: Array.from(disabledSet),
|
|
147
|
+
summary: {
|
|
148
|
+
total: tools.length,
|
|
149
|
+
enabled: tools.filter(t => t.enabled).length,
|
|
150
|
+
disabled: tools.filter(t => !t.enabled).length,
|
|
151
|
+
pluginCount: groups.filter(g => g.isPlugin).length,
|
|
152
|
+
},
|
|
153
|
+
}
|
|
154
|
+
}
|
package/src/client/index.jsx
CHANGED
|
@@ -31,14 +31,15 @@ export function apply(ctx) {
|
|
|
31
31
|
return ok
|
|
32
32
|
}
|
|
33
33
|
const face = { hooks: { manager: controller }, request }
|
|
34
|
-
// The
|
|
34
|
+
// The three sections share one controller and managed patch; each kind gets its own settings page.
|
|
35
35
|
const section = (id, kind) => ctx.slots.inject('settings.section', () => ctx.slots.register({
|
|
36
|
-
name: 'settings.section', id, order: kind === 'skill' ? 16 : 17,
|
|
37
|
-
label: () => t(kind === 'skill' ? 'navSkill' : 'navMcp'), locale: namespace,
|
|
36
|
+
name: 'settings.section', id, order: kind === 'skill' ? 16 : kind === 'mcp' ? 17 : 18,
|
|
37
|
+
label: () => t(kind === 'skill' ? 'navSkill' : kind === 'mcp' ? 'navMcp' : 'navCatalog'), locale: namespace,
|
|
38
38
|
inject: () => face,
|
|
39
|
-
}, kind === 'skill' ? SkillSection : McpSection))
|
|
39
|
+
}, kind === 'skill' ? SkillSection : kind === 'mcp' ? McpSection : CatalogSection))
|
|
40
40
|
section('skill-manager', 'skill')
|
|
41
41
|
section('mcp-manager', 'mcp')
|
|
42
|
+
section('skill-catalog', 'catalog')
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
export function SkillSection({ t, useManager, request }) {
|
|
@@ -387,3 +388,147 @@ function AddForm({ kind, t, busy, onSave, onCancel }) {
|
|
|
387
388
|
<div className="dsh-ext-actions"><button type="button" onClick={onCancel}>{t('cancel')}</button><button type="submit">{t(busy ? 'saving' : 'save')}</button></div>
|
|
388
389
|
</fieldset></form>
|
|
389
390
|
}
|
|
391
|
+
|
|
392
|
+
export function CatalogSection({ t, useManager, request }) {
|
|
393
|
+
const state = useManager(value => value)
|
|
394
|
+
const [query, setQuery] = useState('')
|
|
395
|
+
const [pluginFilter, setPluginFilter] = useState('all')
|
|
396
|
+
const busy = state.loading || state.saving
|
|
397
|
+
const catalog = state.catalog ?? { tools: [], groups: [], summary: { total: 0, enabled: 0, disabled: 0, pluginCount: 0 } }
|
|
398
|
+
const groups = catalog.groups ?? []
|
|
399
|
+
const summary = catalog.summary ?? { total: 0, enabled: 0, disabled: 0, pluginCount: 0 }
|
|
400
|
+
|
|
401
|
+
useLiveRefresh(request)
|
|
402
|
+
|
|
403
|
+
const visibleGroups = groups
|
|
404
|
+
.filter(g => pluginFilter === 'all' || g.id === pluginFilter)
|
|
405
|
+
.map(g => {
|
|
406
|
+
const filteredTools = g.tools.filter(tool => {
|
|
407
|
+
const text = `${tool.name} ${tool.description} ${g.name}`.toLowerCase()
|
|
408
|
+
return text.includes(query.toLowerCase())
|
|
409
|
+
})
|
|
410
|
+
return { ...g, tools: filteredTools }
|
|
411
|
+
})
|
|
412
|
+
.filter(g => g.tools.length > 0)
|
|
413
|
+
|
|
414
|
+
const handleToggleTool = async (toolName, currentEnabled) => {
|
|
415
|
+
await request('catalog-set-tool', { tool: toolName, enabled: !currentEnabled })
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const handleToggleGroup = async (groupTools, targetEnabled) => {
|
|
419
|
+
const names = groupTools.filter(t => t.canToggle).map(t => t.name)
|
|
420
|
+
if (names.length === 0) return
|
|
421
|
+
await request('catalog-set-group', { tools: names, enabled: targetEnabled })
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return <section className="dsh-ext dsh-catalog-section">
|
|
425
|
+
<header className="dsh-ext-heading">
|
|
426
|
+
<div>
|
|
427
|
+
<h2>{t('navCatalog')}</h2>
|
|
428
|
+
<p>{t('catalogIntro')}</p>
|
|
429
|
+
</div>
|
|
430
|
+
<div className="dsh-ext-heading-actions">
|
|
431
|
+
<button type="button" disabled={busy} onClick={() => request('list')}>{t('refresh')}</button>
|
|
432
|
+
</div>
|
|
433
|
+
</header>
|
|
434
|
+
<PageState state={state} t={t} />
|
|
435
|
+
|
|
436
|
+
<div className="dsh-skill-stats">
|
|
437
|
+
<div><strong>{summary.total}</strong><span>{t('stat_catalogTotal')}</span></div>
|
|
438
|
+
<div><strong style={{ color: 'var(--dsw-alias-brand-primary, #3182ce)' }}>{summary.enabled}</strong><span>{t('stat_catalogEnabled')}</span></div>
|
|
439
|
+
<div><strong style={{ color: summary.disabled > 0 ? '#e05252' : 'inherit' }}>{summary.disabled}</strong><span>{t('stat_catalogDisabled')}</span></div>
|
|
440
|
+
<div><strong>{summary.pluginCount}</strong><span>{t('stat_catalogPlugins')}</span></div>
|
|
441
|
+
</div>
|
|
442
|
+
|
|
443
|
+
<label className="dsh-skill-search">
|
|
444
|
+
<span className="dsh-visually-hidden">{t('search')}</span>
|
|
445
|
+
<input
|
|
446
|
+
type="search"
|
|
447
|
+
value={query}
|
|
448
|
+
onChange={event => setQuery(event.target.value)}
|
|
449
|
+
placeholder={t('catalogSearchPlaceholder')}
|
|
450
|
+
/>
|
|
451
|
+
</label>
|
|
452
|
+
|
|
453
|
+
<div className="dsh-skill-filters" style={{ gridTemplateColumns: '1fr' }}>
|
|
454
|
+
<label>{t('filterPlugin')}<select value={pluginFilter} onChange={event => setPluginFilter(event.target.value)}>
|
|
455
|
+
<option value="all">{t('filterAllPlugins')}</option>
|
|
456
|
+
{groups.map(g => <option key={g.id} value={g.id}>{g.name} ({g.total})</option>)}
|
|
457
|
+
</select></label>
|
|
458
|
+
</div>
|
|
459
|
+
|
|
460
|
+
{visibleGroups.length === 0 ? (
|
|
461
|
+
<div className="dsh-ext-card dsh-ext-empty">
|
|
462
|
+
<strong>{t('noCatalogResults')}</strong>
|
|
463
|
+
<p>{t('noCatalogResultsHint')}</p>
|
|
464
|
+
</div>
|
|
465
|
+
) : (
|
|
466
|
+
visibleGroups.map(group => (
|
|
467
|
+
<details key={group.id} className="dsh-catalog-group-card">
|
|
468
|
+
<summary className="dsh-catalog-group-header">
|
|
469
|
+
<div className="dsh-catalog-group-title">
|
|
470
|
+
<h4>{group.name}</h4>
|
|
471
|
+
<span className="dsh-ext-count">{group.enabledCount} / {group.total} 可用</span>
|
|
472
|
+
</div>
|
|
473
|
+
<div className="dsh-catalog-group-actions" onClick={event => event.stopPropagation()}>
|
|
474
|
+
<button
|
|
475
|
+
type="button"
|
|
476
|
+
disabled={busy}
|
|
477
|
+
onClick={event => {
|
|
478
|
+
event.stopPropagation()
|
|
479
|
+
handleToggleGroup(group.tools, true)
|
|
480
|
+
}}
|
|
481
|
+
>{t('enableAll')}</button>
|
|
482
|
+
<button
|
|
483
|
+
type="button"
|
|
484
|
+
disabled={busy}
|
|
485
|
+
onClick={event => {
|
|
486
|
+
event.stopPropagation()
|
|
487
|
+
handleToggleGroup(group.tools, false)
|
|
488
|
+
}}
|
|
489
|
+
>{t('disableAll')}</button>
|
|
490
|
+
</div>
|
|
491
|
+
</summary>
|
|
492
|
+
<div className="dsh-catalog-tool-list">
|
|
493
|
+
{group.tools.map(tool => (
|
|
494
|
+
<div key={tool.name} className="dsh-catalog-tool-item">
|
|
495
|
+
<div className="dsh-catalog-tool-info">
|
|
496
|
+
<div className="dsh-catalog-tool-header">
|
|
497
|
+
<code>{tool.name}</code>
|
|
498
|
+
<span className={`dsh-skill-badge ${tool.enabled ? 'dsh-skill-badge-group' : 'dsh-skill-badge-invalid'}`}>
|
|
499
|
+
{tool.enabled ? t('enabled') : t('disabled')}
|
|
500
|
+
</span>
|
|
501
|
+
{!tool.canToggle && (
|
|
502
|
+
<span className="dsh-skill-badge">{t('toolProtected')}</span>
|
|
503
|
+
)}
|
|
504
|
+
</div>
|
|
505
|
+
{tool.description && (
|
|
506
|
+
<details className="dsh-catalog-tool-details">
|
|
507
|
+
<summary>{t('details')}</summary>
|
|
508
|
+
<p>{tool.description}</p>
|
|
509
|
+
</details>
|
|
510
|
+
)}
|
|
511
|
+
</div>
|
|
512
|
+
<div className="dsh-catalog-switch-wrap">
|
|
513
|
+
<span className="dsh-catalog-switch-label" style={{ color: tool.enabled ? 'var(--dsw-alias-label-primary)' : 'var(--dsw-alias-label-tertiary)' }}>
|
|
514
|
+
{t(tool.enabled ? 'enable' : 'disable')}
|
|
515
|
+
</span>
|
|
516
|
+
<label className="dsh-catalog-switch">
|
|
517
|
+
<input
|
|
518
|
+
type="checkbox"
|
|
519
|
+
checked={tool.enabled}
|
|
520
|
+
disabled={busy || !tool.canToggle}
|
|
521
|
+
onChange={() => handleToggleTool(tool.name, tool.enabled)}
|
|
522
|
+
/>
|
|
523
|
+
<span className="dsh-catalog-slider" />
|
|
524
|
+
</label>
|
|
525
|
+
</div>
|
|
526
|
+
</div>
|
|
527
|
+
))}
|
|
528
|
+
</div>
|
|
529
|
+
</details>
|
|
530
|
+
))
|
|
531
|
+
)}
|
|
532
|
+
</section>
|
|
533
|
+
}
|
|
534
|
+
|
package/src/client/locales.js
CHANGED
|
@@ -36,6 +36,21 @@ export const zh = {
|
|
|
36
36
|
'issue_invalid-frontmatter': 'frontmatter 必须是对象', 'issue_invalid-name': 'name 必须使用 kebab-case',
|
|
37
37
|
'issue_missing-description': '缺少 description', 'issue_invalid-invocation': '调用权限字段不是有效布尔值',
|
|
38
38
|
issue_unreadable: '文件无法读取', 'issue_source-unreadable': 'Skill 路径无法读取',
|
|
39
|
+
navCatalog: '工具管理',
|
|
40
|
+
catalogIntro: '管理系统中已安装各插件与服务的工具,可自由选择启用或禁用。被禁用的工具将被安全拦截。',
|
|
41
|
+
catalogSearchPlaceholder: '搜索工具名、描述或所属插件…',
|
|
42
|
+
stat_catalogTotal: '总工具数',
|
|
43
|
+
stat_catalogEnabled: '已启用',
|
|
44
|
+
stat_catalogDisabled: '已禁用',
|
|
45
|
+
stat_catalogPlugins: '插件/服务',
|
|
46
|
+
enableAll: '全选启用',
|
|
47
|
+
disableAll: '全部禁用',
|
|
48
|
+
filterPlugin: '插件分组',
|
|
49
|
+
filterAllPlugins: '全部插件',
|
|
50
|
+
noCatalogResults: '未找到匹配的工具',
|
|
51
|
+
noCatalogResultsHint: '请尝试修改搜索词或选择其他插件分组。',
|
|
52
|
+
toolProtected: '核心保留',
|
|
53
|
+
toolTakeoverHint: '此工具受 skill-catalog 统一管理。',
|
|
39
54
|
}
|
|
40
55
|
export const en = {
|
|
41
56
|
navSkill: 'Skills', navMcp: 'MCP',
|
|
@@ -74,4 +89,19 @@ export const en = {
|
|
|
74
89
|
'issue_invalid-frontmatter': 'Frontmatter must be an object', 'issue_invalid-name': 'name must use kebab-case',
|
|
75
90
|
'issue_missing-description': 'Missing description', 'issue_invalid-invocation': 'Invocation field is not a valid boolean',
|
|
76
91
|
issue_unreadable: 'File cannot be read', 'issue_source-unreadable': 'Skill path cannot be read',
|
|
92
|
+
navCatalog: 'Tools',
|
|
93
|
+
catalogIntro: 'Manage tools from installed plugins and services. Enable or disable them selectively with safety guards.',
|
|
94
|
+
catalogSearchPlaceholder: 'Search tool name, description or plugin…',
|
|
95
|
+
stat_catalogTotal: 'Total Tools',
|
|
96
|
+
stat_catalogEnabled: 'Enabled',
|
|
97
|
+
stat_catalogDisabled: 'Disabled',
|
|
98
|
+
stat_catalogPlugins: 'Plugins/Services',
|
|
99
|
+
enableAll: 'Enable All',
|
|
100
|
+
disableAll: 'Disable All',
|
|
101
|
+
filterPlugin: 'Plugin Group',
|
|
102
|
+
filterAllPlugins: 'All Plugins',
|
|
103
|
+
noCatalogResults: 'No matching tools found',
|
|
104
|
+
noCatalogResultsHint: 'Try adjusting your search terms or filter.',
|
|
105
|
+
toolProtected: 'Protected',
|
|
106
|
+
toolTakeoverHint: 'This tool is managed under skill-catalog.',
|
|
77
107
|
}
|
package/src/client/styles.css
CHANGED
|
@@ -34,3 +34,39 @@
|
|
|
34
34
|
.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}
|
|
35
35
|
.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}
|
|
36
36
|
@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}}
|
|
37
|
+
|
|
38
|
+
/* Skill Catalog Takeover Styles */
|
|
39
|
+
.dsh-catalog-section{margin-top:14px}
|
|
40
|
+
.dsh-catalog-header-actions{display:flex;align-items:center;gap:8px}
|
|
41
|
+
.dsh-catalog-group-card{margin-top:16px;border:1px solid var(--dsw-alias-border-l4);border-radius:14px;background:var(--dsw-alias-bg-layer-2);overflow:hidden}
|
|
42
|
+
.dsh-catalog-group-header{display:flex;justify-content:space-between;align-items:center;padding:14px 18px;background:color-mix(in srgb,var(--dsw-alias-bg-layer-3) 50%,transparent);border-bottom:1px solid var(--dsw-alias-border-l4);cursor:pointer;user-select:none}
|
|
43
|
+
.dsh-catalog-group-header::-webkit-details-marker{display:none}
|
|
44
|
+
.dsh-catalog-group-header::marker{content:""}
|
|
45
|
+
.dsh-catalog-group-card:not([open]) .dsh-catalog-group-header{border-bottom:0}
|
|
46
|
+
.dsh-catalog-group-title{display:flex;align-items:center;gap:10px}
|
|
47
|
+
.dsh-catalog-group-title::before{content:"▶";display:inline-block;font-size:11px;color:var(--dsw-alias-label-tertiary);transition:transform .2s ease}
|
|
48
|
+
.dsh-catalog-group-card[open] .dsh-catalog-group-title::before{transform:rotate(90deg)}
|
|
49
|
+
.dsh-catalog-group-title h4{margin:0;font-size:14px;font-weight:600}
|
|
50
|
+
.dsh-catalog-group-actions{display:flex;align-items:center;gap:6px}
|
|
51
|
+
.dsh-catalog-group-actions button{padding:4px 10px;font-size:12px;border-radius:7px}
|
|
52
|
+
.dsh-catalog-tool-item{display:flex;justify-content:space-between;align-items:center;gap:16px;padding:14px 18px;border-bottom:1px solid var(--dsw-alias-border-l4);transition:background .15s ease}
|
|
53
|
+
.dsh-catalog-tool-item:last-child{border-bottom:0}
|
|
54
|
+
.dsh-catalog-tool-item:hover{background:color-mix(in srgb,var(--dsw-alias-bg-layer-3) 40%,transparent)}
|
|
55
|
+
.dsh-catalog-tool-info{display:flex;flex-direction:column;gap:5px;min-width:0;flex:1}
|
|
56
|
+
.dsh-catalog-tool-header{display:flex;align-items:center;gap:8px;flex-wrap:wrap}
|
|
57
|
+
.dsh-catalog-tool-header code{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:13px;font-weight:600;color:var(--dsw-alias-brand-primary)}
|
|
58
|
+
.dsh-catalog-tool-info p{margin:0;font-size:12px;color:var(--dsw-alias-label-secondary);line-height:1.5}
|
|
59
|
+
.dsh-catalog-switch-wrap{display:flex;align-items:center;gap:10px;flex-shrink:0}
|
|
60
|
+
.dsh-catalog-switch-label{font-size:12px;font-weight:500}
|
|
61
|
+
.dsh-catalog-switch{position:relative;display:inline-block;width:40px;height:22px;cursor:pointer}
|
|
62
|
+
.dsh-catalog-switch input{opacity:0;width:0;height:0;position:absolute}
|
|
63
|
+
.dsh-catalog-slider{position:absolute;cursor:pointer;inset:0;background:var(--dsw-alias-bg-layer-4,#3b3b3b);border:1px solid var(--dsw-alias-border-l4);border-radius:22px;transition:.2s}
|
|
64
|
+
.dsh-catalog-slider:before{position:absolute;content:"";height:16px;width:16px;left:2px;bottom:2px;background:#fff;border-radius:50%;transition:.2s}
|
|
65
|
+
.dsh-catalog-switch input:checked + .dsh-catalog-slider{background:var(--dsw-alias-brand-primary,#3182ce);border-color:var(--dsw-alias-brand-primary,#3182ce)}
|
|
66
|
+
.dsh-catalog-switch input:checked + .dsh-catalog-slider:before{transform:translateX(18px)}
|
|
67
|
+
.dsh-catalog-switch input:disabled + .dsh-catalog-slider{opacity:.4;cursor:not-allowed}
|
|
68
|
+
.dsh-catalog-tool-details{margin-top:4px;width:100%}
|
|
69
|
+
.dsh-catalog-tool-details summary{width:max-content;cursor:pointer;font-size:12px;color:var(--dsw-alias-label-secondary);user-select:none;transition:color .15s}
|
|
70
|
+
.dsh-catalog-tool-details summary:hover{color:var(--dsw-alias-label-primary)}
|
|
71
|
+
.dsh-catalog-tool-details p{margin:6px 0 2px;font-size:12px;color:var(--dsw-alias-label-tertiary);line-height:1.55;background:var(--dsw-alias-bg-layer-1);padding:8px 12px;border-radius:8px;border:1px solid var(--dsw-alias-border-l4)}
|
|
72
|
+
|
package/src/index.js
CHANGED
|
@@ -6,10 +6,17 @@ import { readFile } from 'node:fs/promises'
|
|
|
6
6
|
import { defineTool } from '@deepseek-ai/dsh-tools'
|
|
7
7
|
import { ExtensionStore } from './store.js'
|
|
8
8
|
import { createWebHandler, createWebHttpHandler } from './web.js'
|
|
9
|
+
import { buildToolCatalog } from './catalog.js'
|
|
9
10
|
|
|
10
11
|
export const name = 'extension-manager'
|
|
11
12
|
export const inject = ['tools', 'skills']
|
|
12
|
-
export const Config = Schema.object({
|
|
13
|
+
export const Config = Schema.object({
|
|
14
|
+
patchPath: Schema.string().default(''),
|
|
15
|
+
'skill-catalog': Schema.object({
|
|
16
|
+
disabledTools: Schema.array(Schema.string()).default([]),
|
|
17
|
+
enabledTools: Schema.array(Schema.string()).default([]),
|
|
18
|
+
}).description('插件工具目录接管配置,控制各插件工具的启用与禁用').default({}),
|
|
19
|
+
})
|
|
13
20
|
|
|
14
21
|
const string = { type: 'string', required: true }
|
|
15
22
|
const output = {
|
|
@@ -33,11 +40,44 @@ export async function apply(ctx, config) {
|
|
|
33
40
|
} catch {}
|
|
34
41
|
|
|
35
42
|
const home = process.env.DSH_HOME || join(homedir(), '.dsh')
|
|
36
|
-
const store = new ExtensionStore(config
|
|
43
|
+
const store = new ExtensionStore(config?.patchPath || join(home, 'profiles', 'web', 'cordis.patch.yml'))
|
|
37
44
|
await store.ensureNonFatalMcpStartup()
|
|
38
45
|
|
|
39
|
-
const
|
|
40
|
-
|
|
46
|
+
const staticCatalog = config?.['skill-catalog'] || {}
|
|
47
|
+
let cachedDisabled = new Set(staticCatalog.disabledTools || [])
|
|
48
|
+
|
|
49
|
+
const refreshCatalogCache = async () => {
|
|
50
|
+
try {
|
|
51
|
+
const persisted = await store.readCatalog(staticCatalog)
|
|
52
|
+
cachedDisabled = new Set(persisted.disabledTools || [])
|
|
53
|
+
} catch {}
|
|
54
|
+
}
|
|
55
|
+
void refreshCatalogCache()
|
|
56
|
+
|
|
57
|
+
// Monotonic guard: 单调拒绝被 skill-catalog 禁用的工具执行
|
|
58
|
+
ctx.tools.guard(exec => {
|
|
59
|
+
if (cachedDisabled.has(exec.name)) {
|
|
60
|
+
return `【Skill Catalog】工具 "${exec.name}" 当前已被禁用。请在设置页面的“工具管理”或配置项中启用后再调用。`
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// Waterfall pre-execute 拦截
|
|
65
|
+
ctx.on('tools/pre-execute', async (exec, next) => {
|
|
66
|
+
if (cachedDisabled.has(exec.name)) {
|
|
67
|
+
return {
|
|
68
|
+
kind: 'deny',
|
|
69
|
+
reason: `【Skill Catalog】工具 "${exec.name}" 已被配置禁用。`,
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
return next()
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
const onCatalogChange = (newDisabledList) => {
|
|
76
|
+
cachedDisabled = new Set(newDisabledList)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const webHandler = createWebHandler(store, ctx, { onCatalogChange, staticCatalog })
|
|
80
|
+
const httpHandler = createWebHttpHandler(store, ctx, { onCatalogChange, staticCatalog })
|
|
41
81
|
|
|
42
82
|
ctx.inject(['connection'], web => {
|
|
43
83
|
web.connection.rpc.handle('/extensions', webHandler)
|
|
@@ -89,4 +129,17 @@ export async function apply(ctx, config) {
|
|
|
89
129
|
skills: await ctx.skills.snapshot({ cwd: args.cwd, scope: exec.agent, signal: exec.signal }),
|
|
90
130
|
mcpTools: ctx.tools.schemas(exec.agent).map(tool => tool.name).filter(name => name.startsWith('mcp__')),
|
|
91
131
|
}))
|
|
132
|
+
|
|
133
|
+
register('extensions_catalog_list', 'List all detected tools, plugin groups, and their enabled status under skill-catalog takeover.', {}, async () => {
|
|
134
|
+
await refreshCatalogCache()
|
|
135
|
+
return buildToolCatalog(ctx, { disabledTools: Array.from(cachedDisabled) })
|
|
136
|
+
})
|
|
137
|
+
register('extensions_catalog_set', 'Enable or disable a specific tool under skill-catalog takeover.', {
|
|
138
|
+
tool: string, enabled: { type: 'boolean', required: true },
|
|
139
|
+
}, async (args, exec) => {
|
|
140
|
+
const updated = await store.setToolEnabled(args.tool, args.enabled, exec.signal)
|
|
141
|
+
cachedDisabled = new Set(updated.disabledTools || [])
|
|
142
|
+
return { tool: args.tool, enabled: args.enabled, catalog: updated }
|
|
143
|
+
})
|
|
92
144
|
}
|
|
145
|
+
|
package/src/store.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Owns a marked block inside a profile Cordis patch while retaining legacy JSON stores. */
|
|
2
|
-
import { open, readFile, rename, unlink, stat } from 'node:fs/promises'
|
|
2
|
+
import { open, readFile, writeFile, rename, unlink, stat } from 'node:fs/promises'
|
|
3
3
|
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'
|
|
@@ -255,4 +255,65 @@ export class ExtensionStore {
|
|
|
255
255
|
rows.splice(index, 1)
|
|
256
256
|
}, signal, expectedRevision)
|
|
257
257
|
}
|
|
258
|
+
|
|
259
|
+
catalogPath() {
|
|
260
|
+
return `${this.path}.catalog.json`
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async readCatalog(staticConfig = {}) {
|
|
264
|
+
let persisted = { disabledTools: [] }
|
|
265
|
+
try {
|
|
266
|
+
const content = await readFile(this.catalogPath(), 'utf8')
|
|
267
|
+
const parsed = JSON.parse(content)
|
|
268
|
+
if (parsed && typeof parsed === 'object') {
|
|
269
|
+
persisted = {
|
|
270
|
+
disabledTools: Array.isArray(parsed.disabledTools) ? parsed.disabledTools : [],
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
} catch {}
|
|
274
|
+
|
|
275
|
+
const staticDisabled = Array.isArray(staticConfig?.disabledTools)
|
|
276
|
+
? staticConfig.disabledTools
|
|
277
|
+
: []
|
|
278
|
+
|
|
279
|
+
const merged = Array.from(new Set([...persisted.disabledTools, ...staticDisabled]))
|
|
280
|
+
return { disabledTools: merged }
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async updateCatalog(updater, signal) {
|
|
284
|
+
signal?.throwIfAborted()
|
|
285
|
+
const current = await this.readCatalog()
|
|
286
|
+
const nextVal = typeof updater === 'function' ? await updater(current) : updater
|
|
287
|
+
const nextConfig = {
|
|
288
|
+
disabledTools: Array.isArray(nextVal?.disabledTools) ? nextVal.disabledTools : [],
|
|
289
|
+
}
|
|
290
|
+
const temp = `${this.catalogPath()}.${randomUUID()}.tmp`
|
|
291
|
+
await writeFile(temp, JSON.stringify(nextConfig, null, 2), 'utf8')
|
|
292
|
+
signal?.throwIfAborted()
|
|
293
|
+
await rename(temp, this.catalogPath())
|
|
294
|
+
return nextConfig
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async setToolEnabled(toolName, enabled, signal) {
|
|
298
|
+
return this.updateCatalog(current => {
|
|
299
|
+
const set = new Set(current.disabledTools)
|
|
300
|
+
if (enabled) {
|
|
301
|
+
set.delete(toolName)
|
|
302
|
+
} else {
|
|
303
|
+
set.add(toolName)
|
|
304
|
+
}
|
|
305
|
+
return { disabledTools: Array.from(set) }
|
|
306
|
+
}, signal)
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
async setGroupEnabled(toolNames, enabled, signal) {
|
|
310
|
+
return this.updateCatalog(current => {
|
|
311
|
+
const set = new Set(current.disabledTools)
|
|
312
|
+
for (const name of toolNames) {
|
|
313
|
+
if (enabled) set.delete(name)
|
|
314
|
+
else set.add(name)
|
|
315
|
+
}
|
|
316
|
+
return { disabledTools: Array.from(set) }
|
|
317
|
+
}, signal)
|
|
318
|
+
}
|
|
258
319
|
}
|
package/src/web.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import Schema from '@deepseek-ai/schemastery'
|
|
3
3
|
import { revisionOf } from './store.js'
|
|
4
4
|
import { createSkill, deleteSkill, inspectSkills, setSkillGroup, setSkillInvocation, updateSkill } from './skills.js'
|
|
5
|
+
import { buildToolCatalog } from './catalog.js'
|
|
5
6
|
|
|
6
7
|
const text = Schema.string().required()
|
|
7
8
|
const schemas = {
|
|
@@ -28,16 +29,23 @@ const schemas = {
|
|
|
28
29
|
modelInvocable: Schema.boolean().required(), userInvocable: Schema.boolean().required(), revision: text,
|
|
29
30
|
}),
|
|
30
31
|
'delete-skill': Schema.object({ sourceId: text, relativePath: text, skillRevision: text, revision: text }),
|
|
32
|
+
'catalog-set-tool': Schema.object({ tool: text, enabled: Schema.boolean().required(), revision: text }),
|
|
33
|
+
'catalog-set-group': Schema.object({ tools: Schema.array(text).required(), enabled: Schema.boolean().required(), revision: text }),
|
|
34
|
+
'catalog-update': Schema.object({ disabledTools: Schema.array(text).required(), revision: text }),
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
/** Return redacted configuration and observed global tool availability. */
|
|
34
|
-
export async function describeExtensions(store, ctx) {
|
|
38
|
+
export async function describeExtensions(store, ctx, options = {}) {
|
|
35
39
|
const rows = await store.read()
|
|
36
|
-
const toolNames = ctx.tools.schemas().map(tool => tool.name)
|
|
40
|
+
const toolNames = ctx.tools ? ctx.tools.schemas().map(tool => tool.name) : []
|
|
37
41
|
const inventory = await inspectSkills(rows)
|
|
42
|
+
const catalogConfig = await store.readCatalog(options.staticCatalog)
|
|
43
|
+
const catalog = buildToolCatalog(ctx, catalogConfig)
|
|
44
|
+
|
|
38
45
|
return {
|
|
39
46
|
revision: revisionOf(rows),
|
|
40
47
|
...inventory,
|
|
48
|
+
catalog,
|
|
41
49
|
extensions: rows.map(row => ({
|
|
42
50
|
id: row.id,
|
|
43
51
|
kind: row.config.serverName ? 'mcp' : 'skill',
|
|
@@ -56,7 +64,7 @@ export async function describeExtensions(store, ctx) {
|
|
|
56
64
|
}
|
|
57
65
|
|
|
58
66
|
/** All writes carry the exact revision shown to the user. */
|
|
59
|
-
export function createWebHandler(store, ctx) {
|
|
67
|
+
export function createWebHandler(store, ctx, options = {}) {
|
|
60
68
|
return async (endpoint, payload, signal) => {
|
|
61
69
|
try {
|
|
62
70
|
if (!Object.hasOwn(schemas, endpoint)) throw new Error('Unknown operation.')
|
|
@@ -76,6 +84,18 @@ export function createWebHandler(store, ctx) {
|
|
|
76
84
|
}
|
|
77
85
|
if (endpoint === 'delete-extension') await store.remove(args.id, signal, args.revision)
|
|
78
86
|
if (endpoint === 'enable') await store.setEnabled(args.id, args.enabled, signal, args.revision)
|
|
87
|
+
if (endpoint === 'catalog-set-tool') {
|
|
88
|
+
const updated = await store.setToolEnabled(args.tool, args.enabled, signal)
|
|
89
|
+
options.onCatalogChange?.(updated.disabledTools)
|
|
90
|
+
}
|
|
91
|
+
if (endpoint === 'catalog-set-group') {
|
|
92
|
+
const updated = await store.setGroupEnabled(args.tools, args.enabled, signal)
|
|
93
|
+
options.onCatalogChange?.(updated.disabledTools)
|
|
94
|
+
}
|
|
95
|
+
if (endpoint === 'catalog-update') {
|
|
96
|
+
const updated = await store.updateCatalog({ disabledTools: args.disabledTools }, signal)
|
|
97
|
+
options.onCatalogChange?.(updated.disabledTools)
|
|
98
|
+
}
|
|
79
99
|
if (['create-skill', 'set-skill-invocation', 'set-skill-group', 'update-skill', 'delete-skill'].includes(endpoint)) {
|
|
80
100
|
const rows = await store.read()
|
|
81
101
|
if (revisionOf(rows) !== args.revision) {
|
|
@@ -87,7 +107,7 @@ export function createWebHandler(store, ctx) {
|
|
|
87
107
|
else if (endpoint === 'update-skill') await updateSkill(rows, args)
|
|
88
108
|
else await deleteSkill(rows, args)
|
|
89
109
|
}
|
|
90
|
-
return { ok: true, value: await describeExtensions(store, ctx) }
|
|
110
|
+
return { ok: true, value: await describeExtensions(store, ctx, options) }
|
|
91
111
|
} catch (error) {
|
|
92
112
|
return { ok: false, error: {
|
|
93
113
|
code: error.code === 'CONFLICT' ? 'extensions/conflict' : 'extensions/rejected',
|
|
@@ -99,8 +119,8 @@ export function createWebHandler(store, ctx) {
|
|
|
99
119
|
}
|
|
100
120
|
|
|
101
121
|
/** HTTP server route handler that satisfies DSH WebServer and RPC envelope protocols. */
|
|
102
|
-
export function createWebHttpHandler(store, ctx) {
|
|
103
|
-
const handler = createWebHandler(store, ctx)
|
|
122
|
+
export function createWebHttpHandler(store, ctx, options = {}) {
|
|
123
|
+
const handler = createWebHandler(store, ctx, options)
|
|
104
124
|
return async (req, res) => {
|
|
105
125
|
if (req.method === 'OPTIONS') {
|
|
106
126
|
res.writeHead(204, {
|