@civilization/deepseek-harness-skill-mcp 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/client.js CHANGED
@@ -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();
@@ -110,10 +121,16 @@ var zh = {
110
121
  create: "\u521B\u5EFA",
111
122
  edit: "\u7F16\u8F91",
112
123
  delete: "\u5220\u9664",
124
+ confirmDelete: "\u786E\u8BA4\u5220\u9664",
125
+ deleting: "\u5220\u9664\u4E2D\u2026",
113
126
  saving: "\u4FDD\u5B58\u4E2D\u2026",
114
127
  loading: "\u52A0\u8F7D\u4E2D\u2026",
115
- deleteExtensionConfirm: "\u786E\u5B9A\u5220\u9664\u8FD9\u4E2A\u914D\u7F6E\u5417\uFF1FSkill \u6765\u6E90\u4E2D\u7684\u6587\u4EF6\u4E0D\u4F1A\u88AB\u5220\u9664\u3002",
116
- deleteSkillConfirm: "\u786E\u5B9A\u6C38\u4E45\u5220\u9664\u8FD9\u4E2A Skill \u53CA\u5176\u76EE\u5F55\u5185\u5BB9\u5417\uFF1F\u6B64\u64CD\u4F5C\u65E0\u6CD5\u64A4\u9500\u3002",
128
+ deleteExtensionTitle: "\u79FB\u9664 Skill \u6765\u6E90",
129
+ deleteMcpTitle: "\u5220\u9664 MCP \u670D\u52A1\u5668",
130
+ deleteSkillTitle: "\u6C38\u4E45\u5220\u9664 Skill",
131
+ 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",
132
+ deleteMcpConfirm: "\u5C06\u5220\u9664 MCP \u670D\u52A1\u5668\u201C{name}\u201D\u53CA\u5176\u8FDE\u63A5\u914D\u7F6E\u3002",
133
+ 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
134
  enabled: "\u914D\u7F6E\u5DF2\u542F\u7528",
118
135
  disabled: "\u5DF2\u7981\u7528",
119
136
  enable: "\u542F\u7528",
@@ -214,10 +231,16 @@ var en = {
214
231
  create: "Create",
215
232
  edit: "Edit",
216
233
  delete: "Delete",
234
+ confirmDelete: "Delete",
235
+ deleting: "Deleting\u2026",
217
236
  saving: "Saving\u2026",
218
237
  loading: "Loading\u2026",
219
- deleteExtensionConfirm: "Delete this configuration? Files inside a Skill source will not be deleted.",
220
- deleteSkillConfirm: "Permanently delete this Skill and its directory contents? This cannot be undone.",
238
+ deleteExtensionTitle: "Remove Skill source",
239
+ deleteMcpTitle: "Delete MCP server",
240
+ deleteSkillTitle: "Permanently delete Skill",
241
+ deleteExtensionConfirm: "Remove \u201C{name}\u201D from this configuration. Skill files in the directory will be kept.",
242
+ deleteMcpConfirm: "Delete MCP server \u201C{name}\u201D and its connection configuration.",
243
+ deleteSkillConfirm: "Permanently delete \u201C{name}\u201D and the resource files in its directory. This cannot be undone.",
221
244
  enabled: "Configured enabled",
222
245
  disabled: "Disabled",
223
246
  enable: "Enable",
@@ -290,7 +313,7 @@ var en = {
290
313
  };
291
314
 
292
315
  // 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";
316
+ 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
317
 
295
318
  // src/client/index.jsx
296
319
  var import_jsx_runtime = require("react/jsx-runtime");
@@ -321,7 +344,7 @@ function apply(ctx) {
321
344
  const t = ctx.locale.bind(namespace);
322
345
  const request = async (endpoint, args, options) => {
323
346
  const ok = await controller.request(endpoint, args, options);
324
- if (ok && (endpoint !== "list" || options?.silent !== true)) refreshSlashCatalog();
347
+ if (ok && refreshesSlashCatalog(endpoint, options)) refreshSlashCatalog();
325
348
  return ok;
326
349
  };
327
350
  const face = { hooks: { manager: controller }, request };
@@ -368,7 +391,7 @@ function SkillSection({ t, useManager, request }) {
368
391
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ext-heading-actions", children: [
369
392
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy || !state.revision, onClick: () => setForm("source"), children: t("add") }),
370
393
  /* @__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") })
394
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy, onClick: () => request("list", {}, { refreshSlashCatalog: true }), children: t("refresh") })
372
395
  ] })
373
396
  ] }),
374
397
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PageState, { state, t }),
@@ -446,51 +469,70 @@ function SkillSection({ t, useManager, request }) {
446
469
  }
447
470
  function SkillSources({ sources, skills, t, busy, request }) {
448
471
  const [editing, setEditing] = (0, import_react.useState)("");
449
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "dsh-ext-card dsh-skill-source-manager", children: [
450
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("summary", { children: [
451
- t("skillSources"),
452
- " ",
453
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-ext-count", children: sources.length })
454
- ] }),
455
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { children: sources.map((source) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
456
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-skill-source-title", children: [
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
- ] })
472
+ const [confirming, setConfirming] = (0, import_react.useState)("");
473
+ const target = sources.find((source) => source.id === confirming);
474
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
475
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("details", { className: "dsh-ext-card dsh-skill-source-manager", children: [
476
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("summary", { children: [
477
+ t("skillSources"),
478
+ " ",
479
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-ext-count", children: sources.length })
467
480
  ] }),
468
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ext-row-actions", children: [
469
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy, onClick: () => setEditing(editing === source.id ? "" : source.id), children: t("edit") }),
470
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
471
- "button",
472
- {
473
- type: "button",
474
- role: "switch",
475
- "aria-checked": source.enabled,
476
- disabled: busy,
477
- onClick: () => request("enable", { id: source.id, enabled: !source.enabled }),
478
- children: t(source.enabled ? "disable" : "enable")
481
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("ul", { children: sources.map((source) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { children: [
482
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-skill-source-title", children: [
483
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: `dsh-ext-status ${source.enabled ? "dsh-ext-status-available" : ""}`, "aria-hidden": "true" }),
484
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { children: [
485
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("strong", { children: [
486
+ source.id,
487
+ " ",
488
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "dsh-ext-count", children: skills.filter((skill) => skill.sourceId === source.id).length })
489
+ ] }),
490
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("code", { children: source.location }),
491
+ source.issue && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("small", { children: t(`issue_${source.issue}`) })
492
+ ] })
493
+ ] }),
494
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ext-row-actions", children: [
495
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", disabled: busy, onClick: () => setEditing(editing === source.id ? "" : source.id), children: t("edit") }),
496
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
497
+ "button",
498
+ {
499
+ type: "button",
500
+ role: "switch",
501
+ "aria-checked": source.enabled,
502
+ disabled: busy,
503
+ onClick: () => request("enable", { id: source.id, enabled: !source.enabled }, { refreshSlashCatalog: true }),
504
+ children: t(source.enabled ? "disable" : "enable")
505
+ }
506
+ ),
507
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dsh-ext-danger", type: "button", disabled: busy, onClick: () => setConfirming(source.id), children: t("delete") })
508
+ ] }),
509
+ editing === source.id && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SkillSourceForm, { source, t, busy, onCancel: () => setEditing(""), onSave: async (args) => {
510
+ if (await request("update-skill-source", args)) setEditing("");
511
+ } })
512
+ ] }, source.id)) })
513
+ ] }),
514
+ target && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
515
+ ConfirmDialog,
516
+ {
517
+ title: t("deleteExtensionTitle"),
518
+ message: t("deleteExtensionConfirm").replace("{name}", target.id),
519
+ t,
520
+ busy,
521
+ onCancel: () => setConfirming(""),
522
+ onConfirm: async () => {
523
+ if (await request("delete-extension", { id: target.id }, { refreshSlashCatalog: true })) {
524
+ setEditing("");
525
+ setConfirming("");
479
526
  }
480
- ),
481
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dsh-ext-danger", type: "button", disabled: busy, onClick: async () => {
482
- if (window.confirm(t("deleteExtensionConfirm")) && await request("delete-extension", { id: source.id })) setEditing("");
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)) })
527
+ }
528
+ }
529
+ )
489
530
  ] });
490
531
  }
491
532
  function McpSection({ t, useManager, request }) {
492
533
  const state = useManager((value) => value);
493
534
  const [form, setForm] = (0, import_react.useState)("");
535
+ const [confirming, setConfirming] = (0, import_react.useState)("");
494
536
  const busy = state.loading || state.saving;
495
537
  const rows = state.extensions.filter((row) => row.kind === "mcp");
496
538
  useLiveRefresh(request);
@@ -539,16 +581,30 @@ function McpSection({ t, useManager, request }) {
539
581
  children: t(row.enabled ? "disable" : "enable")
540
582
  }
541
583
  ),
542
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dsh-ext-danger", type: "button", disabled: busy, onClick: async () => {
543
- if (window.confirm(t("deleteExtensionConfirm")) && await request("delete-extension", { id: row.id })) setForm("");
544
- }, children: t("delete") })
584
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dsh-ext-danger", type: "button", disabled: busy, onClick: () => setConfirming(row.id), children: t("delete") })
545
585
  ] })
546
586
  ] }, row.id)) }),
547
587
  form === "new" && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(McpForm, { t, busy, onCancel: () => setForm(""), onSave: async (args) => {
548
588
  if (await request("add-mcp", args)) setForm("");
549
589
  } })
550
590
  ] }),
551
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-ext-footnote", children: t("mcpFootnote") })
591
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-ext-footnote", children: t("mcpFootnote") }),
592
+ confirming && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
593
+ ConfirmDialog,
594
+ {
595
+ title: t("deleteMcpTitle"),
596
+ message: t("deleteMcpConfirm").replace("{name}", confirming),
597
+ t,
598
+ busy,
599
+ onCancel: () => setConfirming(""),
600
+ onConfirm: async () => {
601
+ if (await request("delete-extension", { id: confirming })) {
602
+ setForm("");
603
+ setConfirming("");
604
+ }
605
+ }
606
+ }
607
+ )
552
608
  ] });
553
609
  }
554
610
  function useLiveRefresh(request) {
@@ -582,6 +638,7 @@ function skillState(skill) {
582
638
  }
583
639
  function SkillRow({ skill, source, groups, t, busy, request }) {
584
640
  const [editing, setEditing] = (0, import_react.useState)(false);
641
+ const [confirming, setConfirming] = (0, import_react.useState)(false);
585
642
  const state = skillState(skill);
586
643
  const invocation = skill.modelInvocable && skill.userInvocable ? "both" : skill.modelInvocable ? "modelOnly" : skill.userInvocable ? "manualOnly" : "none";
587
644
  const statusLabel = !skill.valid ? "skillStatusInvalid" : !skill.sourceEnabled ? "skillStatusSourceDisabled" : skill.shadowed ? "skillStatusShadowed" : "";
@@ -639,21 +696,72 @@ function SkillRow({ skill, source, groups, t, busy, request }) {
639
696
  ] }),
640
697
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ext-row-actions", children: [
641
698
  /* @__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") })
699
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dsh-ext-danger", type: "button", disabled: busy || !skill.valid, onClick: () => setConfirming(true), children: t("delete") })
649
700
  ] })
650
701
  ] })
651
702
  ] }),
652
703
  editing && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SkillEditForm, { skill, t, busy, onCancel: () => setEditing(false), onSave: async (args) => {
653
704
  if (await request("update-skill", args)) setEditing(false);
654
- } })
705
+ } }),
706
+ confirming && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
707
+ ConfirmDialog,
708
+ {
709
+ title: t("deleteSkillTitle"),
710
+ message: t("deleteSkillConfirm").replace("{name}", skill.name),
711
+ t,
712
+ busy,
713
+ onCancel: () => setConfirming(false),
714
+ onConfirm: async () => {
715
+ await request("delete-skill", {
716
+ sourceId: skill.sourceId,
717
+ relativePath: skill.relativePath,
718
+ skillRevision: skill.revision
719
+ });
720
+ }
721
+ }
722
+ )
655
723
  ] }) });
656
724
  }
725
+ function ConfirmDialog({ title, message, t, busy, onConfirm, onCancel }) {
726
+ const dialog = (0, import_react.useRef)(null);
727
+ const cancel = (0, import_react.useRef)(null);
728
+ const titleId = (0, import_react.useId)();
729
+ const messageId = (0, import_react.useId)();
730
+ (0, import_react.useEffect)(() => {
731
+ dialog.current.showModal();
732
+ cancel.current.focus();
733
+ return () => {
734
+ if (dialog.current?.open) dialog.current.close();
735
+ };
736
+ }, []);
737
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
738
+ "dialog",
739
+ {
740
+ ref: dialog,
741
+ className: "dsh-confirm",
742
+ "aria-labelledby": titleId,
743
+ "aria-describedby": messageId,
744
+ onCancel: (event) => {
745
+ event.preventDefault();
746
+ if (!busy) onCancel();
747
+ },
748
+ onMouseDown: (event) => {
749
+ if (event.target === event.currentTarget && !busy) onCancel();
750
+ },
751
+ children: [
752
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "dsh-confirm-icon", "aria-hidden": "true", children: "!" }),
753
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-confirm-copy", children: [
754
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { id: titleId, children: title }),
755
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { id: messageId, children: message })
756
+ ] }),
757
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-confirm-actions", children: [
758
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { ref: cancel, type: "button", disabled: busy, onClick: onCancel, children: t("cancel") }),
759
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { className: "dsh-confirm-delete", type: "button", disabled: busy, onClick: () => void onConfirm(), children: t(busy ? "deleting" : "confirmDelete") })
760
+ ] })
761
+ ]
762
+ }
763
+ );
764
+ }
657
765
  function SkillEditForm({ skill, t, busy, onSave, onCancel }) {
658
766
  const prefix = (0, import_react.useId)();
659
767
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("form", { className: "dsh-skill-edit-form", onSubmit: (event) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@civilization/deepseek-harness-skill-mcp",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Skill path and MCP server management for DeepSeek Harness Web",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -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()
@@ -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 !== 'list' || options?.silent !== true)) refreshSlashCatalog()
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
- return <details className="dsh-ext-card dsh-skill-source-manager"><summary>{t('skillSources')} <span className="dsh-ext-count">{sources.length}</span></summary>
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={async () => {
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={async () => {
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 => {
@@ -7,8 +7,10 @@ export const zh = {
7
7
  emptyMcp: '还没有添加 MCP 服务器', emptyMcpHint: '通过本地命令或 HTTP 地址连接外部工具。',
8
8
  id: '标识', directory: 'Skill 路径', transport: '配置类型', stdio: 'stdio', http: 'HTTP',
9
9
  command: '命令', args: '参数(每行一个)', cwd: '工作目录(可选)', url: '服务器地址',
10
- cancel: '取消', save: '添加', saveChanges: '保存修改', create: '创建', edit: '编辑', delete: '删除', saving: '保存中…', loading: '加载中…',
11
- deleteExtensionConfirm: '确定删除这个配置吗?Skill 来源中的文件不会被删除。', deleteSkillConfirm: '确定永久删除这个 Skill 及其目录内容吗?此操作无法撤销。',
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: '配置已被修改,请刷新后重试。未提交的表单内容已保留。',
@@ -43,8 +45,10 @@ export const en = {
43
45
  emptyMcp: 'No MCP servers yet', emptyMcpHint: 'Connect external tools with a local command or HTTP endpoint.',
44
46
  id: 'Identifier', directory: 'Skill path', transport: 'Type', stdio: 'stdio', http: 'HTTP',
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
- deleteExtensionConfirm: 'Delete this configuration? Files inside a Skill source will not be deleted.', deleteSkillConfirm: 'Permanently delete this Skill and its directory contents? This cannot be undone.',
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.',
@@ -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)}