@civilization/deepseek-harness-skill-mcp 0.3.0 → 0.3.2
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 +16 -3
- package/lib/client.js +60 -6
- package/package.json +11 -10
- package/src/catalog.js +19 -8
- package/src/client/index.jsx +39 -5
- package/src/client/locales.js +6 -0
- package/src/client/styles.css +9 -0
- package/src/mcp-sse.js +86 -21
- package/src/store.js +45 -9
- package/src/web.js +12 -2
package/README.md
CHANGED
|
@@ -88,10 +88,11 @@ Skill 页面支持:
|
|
|
88
88
|
```
|
|
89
89
|
|
|
90
90
|
### 2. 接管与安全拦截机制
|
|
91
|
-
-
|
|
91
|
+
- **自动归类**:自动扫描运行时注册的所有工具,先按 Harness 自带工具名单识别核心工具(归入 `系统核心工具 (Builtin)`),再按命名规则归属第三方插件(如 `session_*_autotask` 归属于 `dsh-autotask`,`drawio_*` 归属于 `dsh-drawio`,`browser_*` 归属于 `dsh-playwright`,`mcp__*` 归属于对应 MCP 服务)。
|
|
92
92
|
- **单调守卫拦截 (`ctx.tools.guard`)**:当工具被配置为禁用时,Cordis 单调守卫将在工具调用前拦截并返回友好提示(`【Skill Catalog 接管】工具 "..." 当前已被禁用`),杜绝 Agent 越权或误调用。
|
|
93
93
|
- **瀑布流预检查 (`tools/pre-execute`)**:在调度层提前做出 `deny` 决策。
|
|
94
|
-
- **核心工具保护**:PTC
|
|
94
|
+
- **核心工具保护**:Harness 自带的核心工具(`read`、`write`、`edit`、`bash`、`glob`、`grep`、`str_replace_editor`、`job_*`、`skill`、`todo_write`、`web_search` 等)一律标记为受保护,开关禁用;PTC 核心传输通道(`run_code`)与内部 catalog 控制工具同样受保护。因此本面板的启停只作用于插件与 MCP 工具,不会让 Agent 失去读写文件或执行命令的基本能力。
|
|
95
|
+
- **名单维护**:核心工具名单随 Harness 版本变化(当前对齐 0.2.0-rc.2 的 `dsh-tool-*`/`dsh-schedule` 包)。若 Harness 新增或改名了自带工具,需同步 `src/catalog.js` 的 `builtinToolNames`,否则新工具会被当成插件工具并允许禁用。
|
|
95
96
|
|
|
96
97
|
### 3. Web 界面操作
|
|
97
98
|
设置中心的“**工具接管**”页面提供:
|
|
@@ -144,7 +145,11 @@ MCP 工具是提供给模型的工具 schema,不是用户 `/` 命令,因此
|
|
|
144
145
|
|
|
145
146
|
当前版本不接受 `env`、认证 header、带凭据或 token 查询参数的 URL。请勿把密钥放进工具参数、MCP 地址或提交到仓库。
|
|
146
147
|
|
|
147
|
-
|
|
148
|
+
### MCP 自动重连与周期配置
|
|
149
|
+
|
|
150
|
+
- **重连周期滑块(3–60 秒,默认 7 秒)**:用户在添加或编辑 MCP 服务器时,可使用滑块指定最大重连等待阈值(`reconnectInterval`)。
|
|
151
|
+
- **无限长效重试保底**:解除底层连续 10 次失败后彻底放弃重试的硬限制,改为长效保底探测模式。当服务端离线或重启时,客户端从 500ms 开始指数退避,并在达到设定的阈值后持续周期探测;只要 MCP 服务端恢复,客户端将在设定的阈值时间内自动重新建立连接并热重载工具。
|
|
152
|
+
- **全协议统一管理**:`stdio`、`streamable-http` 与旧版 `sse` 均统一纳入监督重连体系,端点离线或初次发现失败绝不阻塞 DSH 正常启动。
|
|
148
153
|
|
|
149
154
|
## Agent 工具与 Skill
|
|
150
155
|
|
|
@@ -195,6 +200,14 @@ pnpm run web
|
|
|
195
200
|
|
|
196
201
|
浏览器端 `/` 候选缓存失效兼容逻辑会使用公开的 `connection/reset` 客户端事件;服务端通过注入 `webServer` 与 `connection` 双重挂载 `/extensions` 路由并由 Cordis effect 托管生命周期,防止动态请求掉入前端静态资源的 405 fallback。自动测试验证 Host/RPC、HTTP 桥接和浏览器 bundle 构建,但没有在本仓库内启动完整 Harness Web 做端到端菜单点击验证。外部文件改动仍依赖 filesystem provider 先完成 watcher 失效,必要时可在变更后点击管理页“刷新”。
|
|
197
202
|
|
|
203
|
+
## Harness / Cordis 版本兼容
|
|
204
|
+
|
|
205
|
+
当前版本在 DeepSeek Harness **0.2.1-rc.2** 及 Cordis **4.0.5** 上验证:`pnpm test`(35 项全部通过)与 `pnpm run build` 通过,并逐项核对了所用公开接口——`tools.register/get/schemas/guard`、`tools/pre-execute` 的 `deny` 决策、`skills.register/snapshot`、`webServer.register(kind: 'prefix')`、服务端 `connection.rpc.handle` 与浏览器端 `connection.rpc.call`、`settings.section` 槽位、`locale.register/bind`、`connection/reset` 事件、客户端 bundle 的 `window.__ModuleLoader__.load({ id, factory })` 格式。
|
|
206
|
+
|
|
207
|
+
- `peerDependencies` 只声明**下界**(`@deepseek-ai/cordis: ^4.0.2`,`@deepseek-ai/dsh-tools: >=0.1.5-rc.2`,无上界),以便跟随 Harness 的快速迭代:Harness 的插件兼容性判定用运行时版本比对这些范围。
|
|
208
|
+
- `@deepseek-ai/dsh-mcp-client` 与 `@deepseek-ai/dsh-skill-filesystem` 作为 `dependencies` 对齐至 `0.2.1-rc.2`,并对齐 `@deepseek-ai/schemastery: ~3.18.5`。
|
|
209
|
+
- 工具目录的核心工具名单同样是版本相关数据,见上文“名单维护”。
|
|
210
|
+
|
|
198
211
|
## 发布
|
|
199
212
|
|
|
200
213
|
推送和 Pull Request 会运行构建与测试。推送与 `package.json` 版本一致的 `v*` 标签后,GitHub Actions 会从 `NPM_TOKEN` Actions Secret 发布 npm 包并附带 provenance。例如发布 `0.1.0`:
|
package/lib/client.js
CHANGED
|
@@ -216,7 +216,10 @@ var zh = {
|
|
|
216
216
|
noCatalogResults: "\u672A\u627E\u5230\u5339\u914D\u7684\u5DE5\u5177",
|
|
217
217
|
noCatalogResultsHint: "\u8BF7\u5C1D\u8BD5\u4FEE\u6539\u641C\u7D22\u8BCD\u6216\u9009\u62E9\u5176\u4ED6\u63D2\u4EF6\u5206\u7EC4\u3002",
|
|
218
218
|
toolProtected: "\u6838\u5FC3\u4FDD\u7559",
|
|
219
|
-
toolTakeoverHint: "\u6B64\u5DE5\u5177\u53D7 skill-catalog \u7EDF\u4E00\u7BA1\u7406\u3002"
|
|
219
|
+
toolTakeoverHint: "\u6B64\u5DE5\u5177\u53D7 skill-catalog \u7EDF\u4E00\u7BA1\u7406\u3002",
|
|
220
|
+
reconnectInterval: "\u91CD\u8FDE\u5468\u671F\u9608\u503C",
|
|
221
|
+
reconnectIntervalDesc: "\u670D\u52A1\u7AEF\u65AD\u5F00\u540E\u81EA\u52A8\u63A2\u6D4B\u5E76\u6062\u590D\u8FDE\u63A5\u7684\u6700\u5927\u7B49\u5F85\u65F6\u95F4\uFF08\u8303\u56F4 3\u201360 \u79D2\uFF0C\u9ED8\u8BA4 7 \u79D2\uFF09\u3002",
|
|
222
|
+
seconds: "\u79D2"
|
|
220
223
|
};
|
|
221
224
|
var en = {
|
|
222
225
|
navSkill: "Skills",
|
|
@@ -342,11 +345,14 @@ var en = {
|
|
|
342
345
|
noCatalogResults: "No matching tools found",
|
|
343
346
|
noCatalogResultsHint: "Try adjusting your search terms or filter.",
|
|
344
347
|
toolProtected: "Protected",
|
|
345
|
-
toolTakeoverHint: "This tool is managed under skill-catalog."
|
|
348
|
+
toolTakeoverHint: "This tool is managed under skill-catalog.",
|
|
349
|
+
reconnectInterval: "Reconnect Interval",
|
|
350
|
+
reconnectIntervalDesc: "Maximum wait time before retrying dropped connections (range 3\u201360s, default 7s).",
|
|
351
|
+
seconds: "s"
|
|
346
352
|
};
|
|
347
353
|
|
|
348
354
|
// src/client/styles.css
|
|
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';
|
|
355
|
+
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/* MCP Reconnect Slider */\n.dsh-mcp-slider-group{grid-column:1/-1;display:flex;flex-direction:column;gap:8px;padding:12px 14px;border-radius:10px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l4)}\n.dsh-mcp-slider-header{display:flex;justify-content:space-between;align-items:center}\n.dsh-mcp-slider-header label{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary)}\n.dsh-mcp-slider-badge{font-size:12px;font-weight:700;padding:2px 8px;border-radius:999px;color:var(--dsw-alias-brand-primary);background:color-mix(in srgb,var(--dsw-alias-brand-primary) 14%,transparent)}\n.dsh-mcp-slider-group input[type=range]{width:100%;cursor:pointer;accent-color:var(--dsw-alias-brand-primary);margin:4px 0}\n.dsh-mcp-slider-hint{margin:0;font-size:12px;color:var(--dsw-alias-label-tertiary);line-height:1.4}\n\n\n';
|
|
350
356
|
|
|
351
357
|
// src/client/index.jsx
|
|
352
358
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
@@ -962,8 +968,9 @@ function SkillSourceForm({ source, t, busy, onSave, onCancel }) {
|
|
|
962
968
|
}
|
|
963
969
|
function McpForm({ row, t, busy, onSave, onCancel }) {
|
|
964
970
|
const prefix = (0, import_react.useId)();
|
|
965
|
-
const initial = row?.configuration ?? { transport: "stdio", command: "", args: [], cwd: "" };
|
|
971
|
+
const initial = row?.configuration ?? { transport: "stdio", command: "", args: [], cwd: "", reconnectInterval: 7 };
|
|
966
972
|
const [transport, setTransport] = (0, import_react.useState)(initial.transport);
|
|
973
|
+
const [reconnectInterval, setReconnectInterval] = (0, import_react.useState)(initial.reconnectInterval ?? 7);
|
|
967
974
|
const field = (name, required = true, multiline = false, value = "") => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { htmlFor: `${prefix}-${name}`, children: [
|
|
968
975
|
t(name),
|
|
969
976
|
multiline ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", { id: `${prefix}-${name}`, name, rows: 3, defaultValue: value }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { id: `${prefix}-${name}`, name, required, autoComplete: "off", defaultValue: value, ...name === "id" ? { pattern: "[A-Za-z0-9_\\-]{1,32}", maxLength: 32 } : {} })
|
|
@@ -971,7 +978,7 @@ function McpForm({ row, t, busy, onSave, onCancel }) {
|
|
|
971
978
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("form", { className: row ? "dsh-ext-inline-form" : "dsh-ext-form", onSubmit: (event) => {
|
|
972
979
|
event.preventDefault();
|
|
973
980
|
const data = Object.fromEntries(new FormData(event.currentTarget));
|
|
974
|
-
const configuration = JSON.stringify(transport === "stdio" ? { transport, command: data.command, args: data.args.split(/\r?\n/).filter(Boolean), cwd: data.cwd } : { transport, url: data.url });
|
|
981
|
+
const configuration = JSON.stringify(transport === "stdio" ? { transport, command: data.command, args: data.args.split(/\r?\n/).filter(Boolean), cwd: data.cwd, reconnectInterval } : { transport, url: data.url, reconnectInterval });
|
|
975
982
|
void onSave({ id: row?.id ?? data.id, configuration });
|
|
976
983
|
}, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("fieldset", { disabled: busy, children: [
|
|
977
984
|
!row && field("id"),
|
|
@@ -988,6 +995,29 @@ function McpForm({ row, t, busy, onSave, onCancel }) {
|
|
|
988
995
|
field("args", false, true, (initial.args ?? []).join("\n")),
|
|
989
996
|
field("cwd", false, false, initial.cwd)
|
|
990
997
|
] }) : field("url", true, false, initial.url),
|
|
998
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-mcp-slider-group", children: [
|
|
999
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-mcp-slider-header", children: [
|
|
1000
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: `${prefix}-reconnect`, children: t("reconnectInterval") }),
|
|
1001
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsh-mcp-slider-badge", children: [
|
|
1002
|
+
reconnectInterval,
|
|
1003
|
+
" ",
|
|
1004
|
+
t("seconds")
|
|
1005
|
+
] })
|
|
1006
|
+
] }),
|
|
1007
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1008
|
+
"input",
|
|
1009
|
+
{
|
|
1010
|
+
id: `${prefix}-reconnect`,
|
|
1011
|
+
type: "range",
|
|
1012
|
+
min: "3",
|
|
1013
|
+
max: "60",
|
|
1014
|
+
step: "1",
|
|
1015
|
+
value: reconnectInterval,
|
|
1016
|
+
onChange: (event) => setReconnectInterval(Number(event.target.value))
|
|
1017
|
+
}
|
|
1018
|
+
),
|
|
1019
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-mcp-slider-hint", children: t("reconnectIntervalDesc") })
|
|
1020
|
+
] }),
|
|
991
1021
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: t("secret") }),
|
|
992
1022
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ext-actions", children: [
|
|
993
1023
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("button", { type: "button", onClick: onCancel, children: t("cancel") }),
|
|
@@ -998,6 +1028,7 @@ function McpForm({ row, t, busy, onSave, onCancel }) {
|
|
|
998
1028
|
function AddForm({ kind, t, busy, onSave, onCancel }) {
|
|
999
1029
|
const prefix = (0, import_react.useId)();
|
|
1000
1030
|
const [transport, setTransport] = (0, import_react.useState)("stdio");
|
|
1031
|
+
const [reconnectInterval, setReconnectInterval] = (0, import_react.useState)(7);
|
|
1001
1032
|
const field = (name, required = true, multiline = false) => /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("label", { htmlFor: `${prefix}-${name}`, children: [
|
|
1002
1033
|
t(name),
|
|
1003
1034
|
multiline ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("textarea", { id: `${prefix}-${name}`, name, rows: 3 }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("input", { id: `${prefix}-${name}`, name, required, autoComplete: "off", ...name === "id" ? { pattern: "[A-Za-z0-9_\\-]{1,32}", maxLength: 32 } : {} })
|
|
@@ -1007,7 +1038,7 @@ function AddForm({ kind, t, busy, onSave, onCancel }) {
|
|
|
1007
1038
|
const data = Object.fromEntries(new FormData(event.currentTarget));
|
|
1008
1039
|
const args = kind === "skill" ? { directory: data.directory } : {
|
|
1009
1040
|
id: data.id,
|
|
1010
|
-
configuration: JSON.stringify(transport === "stdio" ? { transport, command: data.command, args: data.args.split(/\r?\n/).filter(Boolean), cwd: data.cwd } : { transport, url: data.url })
|
|
1041
|
+
configuration: JSON.stringify(transport === "stdio" ? { transport, command: data.command, args: data.args.split(/\r?\n/).filter(Boolean), cwd: data.cwd, reconnectInterval } : { transport, url: data.url, reconnectInterval })
|
|
1011
1042
|
};
|
|
1012
1043
|
void onSave(args);
|
|
1013
1044
|
}, children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("fieldset", { disabled: busy, children: [
|
|
@@ -1026,6 +1057,29 @@ function AddForm({ kind, t, busy, onSave, onCancel }) {
|
|
|
1026
1057
|
field("args", false, true),
|
|
1027
1058
|
field("cwd", false)
|
|
1028
1059
|
] }) : field("url"),
|
|
1060
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-mcp-slider-group", children: [
|
|
1061
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-mcp-slider-header", children: [
|
|
1062
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("label", { htmlFor: `${prefix}-reconnect`, children: t("reconnectInterval") }),
|
|
1063
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dsh-mcp-slider-badge", children: [
|
|
1064
|
+
reconnectInterval,
|
|
1065
|
+
" ",
|
|
1066
|
+
t("seconds")
|
|
1067
|
+
] })
|
|
1068
|
+
] }),
|
|
1069
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
1070
|
+
"input",
|
|
1071
|
+
{
|
|
1072
|
+
id: `${prefix}-reconnect`,
|
|
1073
|
+
type: "range",
|
|
1074
|
+
min: "3",
|
|
1075
|
+
max: "60",
|
|
1076
|
+
step: "1",
|
|
1077
|
+
value: reconnectInterval,
|
|
1078
|
+
onChange: (event) => setReconnectInterval(Number(event.target.value))
|
|
1079
|
+
}
|
|
1080
|
+
),
|
|
1081
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "dsh-mcp-slider-hint", children: t("reconnectIntervalDesc") })
|
|
1082
|
+
] }),
|
|
1029
1083
|
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { children: t("secret") })
|
|
1030
1084
|
] }),
|
|
1031
1085
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "dsh-ext-actions", children: [
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@civilization/deepseek-harness-skill-mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Skill path and MCP server management for DeepSeek Harness Web",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -44,28 +44,29 @@
|
|
|
44
44
|
"test": "node --test tests/*.test.js",
|
|
45
45
|
"build": "node scripts/build.js",
|
|
46
46
|
"web": "node scripts/web.js",
|
|
47
|
-
"setup": "node scripts/setup.js"
|
|
47
|
+
"setup": "node scripts/setup.js",
|
|
48
|
+
"prepack": "pnpm build"
|
|
48
49
|
},
|
|
49
50
|
"engines": {
|
|
50
51
|
"node": ">=22.19.0"
|
|
51
52
|
},
|
|
52
53
|
"packageManager": "pnpm@10.33.0",
|
|
53
54
|
"dependencies": {
|
|
54
|
-
"@deepseek-ai/dsh-mcp-client": "0.1
|
|
55
|
-
"@deepseek-ai/dsh-skill-filesystem": "0.1
|
|
56
|
-
"@deepseek-ai/schemastery": "3.18.
|
|
55
|
+
"@deepseek-ai/dsh-mcp-client": "0.2.1-rc.2",
|
|
56
|
+
"@deepseek-ai/dsh-skill-filesystem": "0.2.1-rc.2",
|
|
57
|
+
"@deepseek-ai/schemastery": "~3.18.5",
|
|
57
58
|
"@modelcontextprotocol/sdk": "1.29.0",
|
|
58
59
|
"yaml": "^2.9.0"
|
|
59
60
|
},
|
|
60
61
|
"peerDependencies": {
|
|
61
62
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
62
|
-
"@deepseek-ai/dsh-tools": "
|
|
63
|
+
"@deepseek-ai/dsh-tools": ">=0.1.5-rc.2"
|
|
63
64
|
},
|
|
64
65
|
"devDependencies": {
|
|
65
|
-
"@deepseek-ai/cordis": "4.0.
|
|
66
|
-
"@deepseek-ai/dsh-skill": "0.1
|
|
67
|
-
"@deepseek-ai/dsh-system-prompt": "0.1
|
|
68
|
-
"@deepseek-ai/dsh-tools": "0.1
|
|
66
|
+
"@deepseek-ai/cordis": "4.0.5",
|
|
67
|
+
"@deepseek-ai/dsh-skill": "0.2.1-rc.2",
|
|
68
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.1-rc.2",
|
|
69
|
+
"@deepseek-ai/dsh-tools": "0.2.1-rc.2",
|
|
69
70
|
"esbuild": "^0.28.2",
|
|
70
71
|
"react": "^19.2.8"
|
|
71
72
|
}
|
package/src/catalog.js
CHANGED
|
@@ -1,13 +1,22 @@
|
|
|
1
1
|
/** Plugin tool discovery, classification, and execution takeover management. */
|
|
2
2
|
|
|
3
|
-
/**
|
|
3
|
+
/**
|
|
4
|
+
* Tool names shipped by the Harness runtime itself, verified against the
|
|
5
|
+
* `dsh-tool-*`/`dsh-schedule` packages of DSH 0.2.0-rc.2. Core tools are listed,
|
|
6
|
+
* grouped as `Builtin`, and protected from execution takeover, so a stale entry
|
|
7
|
+
* here is a correctness bug rather than cosmetics: unknown core tools would be
|
|
8
|
+
* labelled as plugins and become disableable. Refresh this set when a Harness
|
|
9
|
+
* release adds or renames a bundled tool.
|
|
10
|
+
*/
|
|
4
11
|
const builtinToolNames = new Set([
|
|
5
|
-
'
|
|
6
|
-
'
|
|
7
|
-
'
|
|
8
|
-
'
|
|
9
|
-
'
|
|
10
|
-
'
|
|
12
|
+
'ask_user_question', 'bash', 'pwsh', 'read', 'read_image', 'write', 'edit', 'str_replace_editor',
|
|
13
|
+
'glob', 'grep', 'present', 'skill', 'todo_write',
|
|
14
|
+
'create_goal', 'get_goal', 'update_goal',
|
|
15
|
+
'job_list', 'job_output', 'job_kill',
|
|
16
|
+
'subagent', 'subagent_fork', 'list_agents', 'list_subagent_models', 'send_message', 'interrupt_agent',
|
|
17
|
+
'web_fetch', 'web_search', 'load_workspace_dependencies', 'ralph', 'workflow',
|
|
18
|
+
'schedule_create', 'schedule_delete', 'schedule_list', 'schedule_update',
|
|
19
|
+
'cordis_inspect_list', 'cordis_inspect_query',
|
|
11
20
|
])
|
|
12
21
|
|
|
13
22
|
/**
|
|
@@ -111,7 +120,9 @@ export function buildToolCatalog(ctx, catalogConfig = {}) {
|
|
|
111
120
|
groupName: classification.name,
|
|
112
121
|
isPlugin: classification.isPlugin,
|
|
113
122
|
enabled,
|
|
114
|
-
|
|
123
|
+
// Takeover targets plugin-registered tools only: disabling a bundled core
|
|
124
|
+
// tool from this panel could leave the agent unable to read or edit files.
|
|
125
|
+
canToggle: classification.isPlugin && schema.name !== 'run_code' && !schema.name.startsWith('extensions_catalog_'),
|
|
115
126
|
}
|
|
116
127
|
})
|
|
117
128
|
|
package/src/client/index.jsx
CHANGED
|
@@ -343,8 +343,9 @@ function SkillSourceForm({ source, t, busy, onSave, onCancel }) {
|
|
|
343
343
|
|
|
344
344
|
function McpForm({ row, t, busy, onSave, onCancel }) {
|
|
345
345
|
const prefix = useId()
|
|
346
|
-
const initial = row?.configuration ?? { transport: 'stdio', command: '', args: [], cwd: '' }
|
|
346
|
+
const initial = row?.configuration ?? { transport: 'stdio', command: '', args: [], cwd: '', reconnectInterval: 7 }
|
|
347
347
|
const [transport, setTransport] = useState(initial.transport)
|
|
348
|
+
const [reconnectInterval, setReconnectInterval] = useState(initial.reconnectInterval ?? 7)
|
|
348
349
|
const field = (name, required = true, multiline = false, value = '') => <label htmlFor={`${prefix}-${name}`}>{t(name)}{multiline
|
|
349
350
|
? <textarea id={`${prefix}-${name}`} name={name} rows={3} defaultValue={value} />
|
|
350
351
|
: <input id={`${prefix}-${name}`} name={name} required={required} autoComplete="off" defaultValue={value} {...(name === 'id' ? { pattern: '[A-Za-z0-9_\\-]{1,32}', maxLength: 32 } : {})} />}</label>
|
|
@@ -352,13 +353,29 @@ function McpForm({ row, t, busy, onSave, onCancel }) {
|
|
|
352
353
|
event.preventDefault()
|
|
353
354
|
const data = Object.fromEntries(new FormData(event.currentTarget))
|
|
354
355
|
const configuration = JSON.stringify(transport === 'stdio'
|
|
355
|
-
? { transport, command: data.command, args: data.args.split(/\r?\n/).filter(Boolean), cwd: data.cwd }
|
|
356
|
-
: { transport, url: data.url })
|
|
356
|
+
? { transport, command: data.command, args: data.args.split(/\r?\n/).filter(Boolean), cwd: data.cwd, reconnectInterval }
|
|
357
|
+
: { transport, url: data.url, reconnectInterval })
|
|
357
358
|
void onSave({ id: row?.id ?? data.id, configuration })
|
|
358
359
|
}}><fieldset disabled={busy}>{!row && field('id')}
|
|
359
360
|
<label htmlFor={`${prefix}-transport`}>{t('transport')}<select id={`${prefix}-transport`} value={transport} onChange={event => setTransport(event.target.value)}>
|
|
360
361
|
<option value="stdio">{t('stdio')}</option><option value="streamable-http">{t('http')}</option><option value="sse">{t('sse')}</option></select></label>
|
|
361
362
|
{transport === 'stdio' ? <>{field('command', true, false, initial.command)}{field('args', false, true, (initial.args ?? []).join('\n'))}{field('cwd', false, false, initial.cwd)}</> : field('url', true, false, initial.url)}
|
|
363
|
+
<div className="dsh-mcp-slider-group">
|
|
364
|
+
<div className="dsh-mcp-slider-header">
|
|
365
|
+
<label htmlFor={`${prefix}-reconnect`}>{t('reconnectInterval')}</label>
|
|
366
|
+
<span className="dsh-mcp-slider-badge">{reconnectInterval} {t('seconds')}</span>
|
|
367
|
+
</div>
|
|
368
|
+
<input
|
|
369
|
+
id={`${prefix}-reconnect`}
|
|
370
|
+
type="range"
|
|
371
|
+
min="3"
|
|
372
|
+
max="60"
|
|
373
|
+
step="1"
|
|
374
|
+
value={reconnectInterval}
|
|
375
|
+
onChange={event => setReconnectInterval(Number(event.target.value))}
|
|
376
|
+
/>
|
|
377
|
+
<p className="dsh-mcp-slider-hint">{t('reconnectIntervalDesc')}</p>
|
|
378
|
+
</div>
|
|
362
379
|
<p>{t('secret')}</p>
|
|
363
380
|
<div className="dsh-ext-actions"><button type="button" onClick={onCancel}>{t('cancel')}</button><button type="submit">{t(busy ? 'saving' : row ? 'saveChanges' : 'save')}</button></div>
|
|
364
381
|
</fieldset></form>
|
|
@@ -367,6 +384,7 @@ function McpForm({ row, t, busy, onSave, onCancel }) {
|
|
|
367
384
|
function AddForm({ kind, t, busy, onSave, onCancel }) {
|
|
368
385
|
const prefix = useId()
|
|
369
386
|
const [transport, setTransport] = useState('stdio')
|
|
387
|
+
const [reconnectInterval, setReconnectInterval] = useState(7)
|
|
370
388
|
const field = (name, required = true, multiline = false) => <label htmlFor={`${prefix}-${name}`}>{t(name)}{multiline
|
|
371
389
|
? <textarea id={`${prefix}-${name}`} name={name} rows={3} />
|
|
372
390
|
: <input id={`${prefix}-${name}`} name={name} required={required} autoComplete="off" {...(name === 'id' ? { pattern: '[A-Za-z0-9_\\-]{1,32}', maxLength: 32 } : {})} />}</label>
|
|
@@ -375,14 +393,30 @@ function AddForm({ kind, t, busy, onSave, onCancel }) {
|
|
|
375
393
|
const data = Object.fromEntries(new FormData(event.currentTarget))
|
|
376
394
|
const args = kind === 'skill' ? { directory: data.directory } : {
|
|
377
395
|
id: data.id, configuration: JSON.stringify(transport === 'stdio'
|
|
378
|
-
? { transport, command: data.command, args: data.args.split(/\r?\n/).filter(Boolean), cwd: data.cwd }
|
|
379
|
-
: { transport, url: data.url }),
|
|
396
|
+
? { transport, command: data.command, args: data.args.split(/\r?\n/).filter(Boolean), cwd: data.cwd, reconnectInterval }
|
|
397
|
+
: { transport, url: data.url, reconnectInterval }),
|
|
380
398
|
}
|
|
381
399
|
void onSave(args)
|
|
382
400
|
}}><fieldset disabled={busy}>{kind === 'skill' ? field('directory') : <>{field('id')}
|
|
383
401
|
<label htmlFor={`${prefix}-transport`}>{t('transport')}<select id={`${prefix}-transport`} value={transport} onChange={event => setTransport(event.target.value)}>
|
|
384
402
|
<option value="stdio">{t('stdio')}</option><option value="streamable-http">{t('http')}</option><option value="sse">{t('sse')}</option></select></label>
|
|
385
403
|
{transport === 'stdio' ? <>{field('command')}{field('args', false, true)}{field('cwd', false)}</> : field('url')}
|
|
404
|
+
<div className="dsh-mcp-slider-group">
|
|
405
|
+
<div className="dsh-mcp-slider-header">
|
|
406
|
+
<label htmlFor={`${prefix}-reconnect`}>{t('reconnectInterval')}</label>
|
|
407
|
+
<span className="dsh-mcp-slider-badge">{reconnectInterval} {t('seconds')}</span>
|
|
408
|
+
</div>
|
|
409
|
+
<input
|
|
410
|
+
id={`${prefix}-reconnect`}
|
|
411
|
+
type="range"
|
|
412
|
+
min="3"
|
|
413
|
+
max="60"
|
|
414
|
+
step="1"
|
|
415
|
+
value={reconnectInterval}
|
|
416
|
+
onChange={event => setReconnectInterval(Number(event.target.value))}
|
|
417
|
+
/>
|
|
418
|
+
<p className="dsh-mcp-slider-hint">{t('reconnectIntervalDesc')}</p>
|
|
419
|
+
</div>
|
|
386
420
|
<p>{t('secret')}</p>
|
|
387
421
|
</>}
|
|
388
422
|
<div className="dsh-ext-actions"><button type="button" onClick={onCancel}>{t('cancel')}</button><button type="submit">{t(busy ? 'saving' : 'save')}</button></div>
|
package/src/client/locales.js
CHANGED
|
@@ -51,6 +51,9 @@ export const zh = {
|
|
|
51
51
|
noCatalogResultsHint: '请尝试修改搜索词或选择其他插件分组。',
|
|
52
52
|
toolProtected: '核心保留',
|
|
53
53
|
toolTakeoverHint: '此工具受 skill-catalog 统一管理。',
|
|
54
|
+
reconnectInterval: '重连周期阈值',
|
|
55
|
+
reconnectIntervalDesc: '服务端断开后自动探测并恢复连接的最大等待时间(范围 3–60 秒,默认 7 秒)。',
|
|
56
|
+
seconds: '秒',
|
|
54
57
|
}
|
|
55
58
|
export const en = {
|
|
56
59
|
navSkill: 'Skills', navMcp: 'MCP',
|
|
@@ -104,4 +107,7 @@ export const en = {
|
|
|
104
107
|
noCatalogResultsHint: 'Try adjusting your search terms or filter.',
|
|
105
108
|
toolProtected: 'Protected',
|
|
106
109
|
toolTakeoverHint: 'This tool is managed under skill-catalog.',
|
|
110
|
+
reconnectInterval: 'Reconnect Interval',
|
|
111
|
+
reconnectIntervalDesc: 'Maximum wait time before retrying dropped connections (range 3–60s, default 7s).',
|
|
112
|
+
seconds: 's',
|
|
107
113
|
}
|
package/src/client/styles.css
CHANGED
|
@@ -70,3 +70,12 @@
|
|
|
70
70
|
.dsh-catalog-tool-details summary:hover{color:var(--dsw-alias-label-primary)}
|
|
71
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
72
|
|
|
73
|
+
/* MCP Reconnect Slider */
|
|
74
|
+
.dsh-mcp-slider-group{grid-column:1/-1;display:flex;flex-direction:column;gap:8px;padding:12px 14px;border-radius:10px;background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l4)}
|
|
75
|
+
.dsh-mcp-slider-header{display:flex;justify-content:space-between;align-items:center}
|
|
76
|
+
.dsh-mcp-slider-header label{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary)}
|
|
77
|
+
.dsh-mcp-slider-badge{font-size:12px;font-weight:700;padding:2px 8px;border-radius:999px;color:var(--dsw-alias-brand-primary);background:color-mix(in srgb,var(--dsw-alias-brand-primary) 14%,transparent)}
|
|
78
|
+
.dsh-mcp-slider-group input[type=range]{width:100%;cursor:pointer;accent-color:var(--dsw-alias-brand-primary);margin:4px 0}
|
|
79
|
+
.dsh-mcp-slider-hint{margin:0;font-size:12px;color:var(--dsw-alias-label-tertiary);line-height:1.4}
|
|
80
|
+
|
|
81
|
+
|
package/src/mcp-sse.js
CHANGED
|
@@ -18,12 +18,26 @@ export const Config = Schema.object({
|
|
|
18
18
|
url: Schema.string().required(),
|
|
19
19
|
toolCallTimeoutMs: Schema.number().default(60_000),
|
|
20
20
|
failOnStartupError: Schema.boolean().default(false),
|
|
21
|
+
reconnect: Schema.object({
|
|
22
|
+
enabled: Schema.boolean().default(true),
|
|
23
|
+
initialDelayMs: Schema.number().default(500),
|
|
24
|
+
maxDelayMs: Schema.number().default(7000),
|
|
25
|
+
maxAttempts: Schema.number().default(Number.MAX_SAFE_INTEGER),
|
|
26
|
+
}).default({}),
|
|
21
27
|
})
|
|
22
28
|
|
|
23
29
|
export async function apply(ctx, config) {
|
|
24
|
-
const
|
|
25
|
-
|
|
30
|
+
const reconnectPolicy = {
|
|
31
|
+
enabled: config.reconnect?.enabled ?? true,
|
|
32
|
+
initialDelayMs: config.reconnect?.initialDelayMs ?? 500,
|
|
33
|
+
maxDelayMs: config.reconnect?.maxDelayMs ?? 7000,
|
|
34
|
+
maxAttempts: config.reconnect?.maxAttempts ?? Number.MAX_SAFE_INTEGER,
|
|
35
|
+
}
|
|
36
|
+
|
|
26
37
|
let disposed = false
|
|
38
|
+
let reconnectTimer
|
|
39
|
+
let failedAttempts = 0
|
|
40
|
+
let currentGeneration
|
|
27
41
|
let registrations = new Map()
|
|
28
42
|
let synchronization = Promise.resolve()
|
|
29
43
|
|
|
@@ -31,13 +45,25 @@ export async function apply(ctx, config) {
|
|
|
31
45
|
for (const dispose of registrations.values()) dispose()
|
|
32
46
|
registrations = new Map()
|
|
33
47
|
}
|
|
34
|
-
ctx.effect(() => () => {
|
|
35
|
-
disposed = true
|
|
36
|
-
clearTools()
|
|
37
|
-
void client.close().catch(() => {})
|
|
38
|
-
}, 'mcp-sse-client.connection')
|
|
39
48
|
|
|
40
|
-
const
|
|
49
|
+
const scheduleReconnect = (cause) => {
|
|
50
|
+
if (disposed || !reconnectPolicy.enabled) return
|
|
51
|
+
if (reconnectTimer !== undefined) return
|
|
52
|
+
failedAttempts++
|
|
53
|
+
if (failedAttempts > reconnectPolicy.maxAttempts) {
|
|
54
|
+
ctx.logger.error(`mcp-sse-client(${config.serverName}): giving up after ${reconnectPolicy.maxAttempts} failed reconnect attempts`)
|
|
55
|
+
return
|
|
56
|
+
}
|
|
57
|
+
const delayMs = Math.min(reconnectPolicy.maxDelayMs, reconnectPolicy.initialDelayMs * (2 ** Math.min(failedAttempts - 1, 30)))
|
|
58
|
+
ctx.logger.warn(`mcp-sse-client(${config.serverName}): connection lost or failed (${messageOf(cause)}); reconnecting in ${delayMs}ms (attempt ${failedAttempts})`)
|
|
59
|
+
reconnectTimer = setTimeout(() => {
|
|
60
|
+
reconnectTimer = undefined
|
|
61
|
+
void connectGeneration(false)
|
|
62
|
+
}, delayMs)
|
|
63
|
+
reconnectTimer.unref?.()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const synchronizeTools = async (client, generation) => {
|
|
41
67
|
const definitions = new Map()
|
|
42
68
|
let cursor
|
|
43
69
|
do {
|
|
@@ -52,7 +78,7 @@ export async function apply(ctx, config) {
|
|
|
52
78
|
}
|
|
53
79
|
cursor = response.nextCursor
|
|
54
80
|
} while (cursor)
|
|
55
|
-
if (disposed) return
|
|
81
|
+
if (disposed || generation.closed) return
|
|
56
82
|
clearTools()
|
|
57
83
|
const next = new Map()
|
|
58
84
|
try {
|
|
@@ -64,21 +90,60 @@ export async function apply(ctx, config) {
|
|
|
64
90
|
}
|
|
65
91
|
}
|
|
66
92
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
93
|
+
async function connectGeneration(startup) {
|
|
94
|
+
if (disposed) return
|
|
95
|
+
const client = new Client({ name: `dsh-sse-${config.serverName}`, version: '1.0.0' }, { capabilities: {} })
|
|
96
|
+
const transport = new SSEClientTransport(new URL(config.url))
|
|
97
|
+
const generation = { client, transport, closed: false }
|
|
98
|
+
currentGeneration = generation
|
|
99
|
+
|
|
100
|
+
const onDisconnect = (err) => {
|
|
101
|
+
if (generation.closed || disposed) return
|
|
102
|
+
generation.closed = true
|
|
103
|
+
clearTools()
|
|
104
|
+
void client.close().catch(() => {})
|
|
105
|
+
if (currentGeneration === generation) {
|
|
106
|
+
scheduleReconnect(err ?? new Error('connection closed'))
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
client.onclose = () => onDisconnect()
|
|
111
|
+
client.onerror = (err) => onDisconnect(err)
|
|
112
|
+
|
|
113
|
+
client.setNotificationHandler(ToolListChangedNotificationSchema, () => {
|
|
114
|
+
if (generation.closed || disposed) return
|
|
115
|
+
synchronization = synchronization.then(() => synchronizeTools(client, generation)).catch(error => {
|
|
116
|
+
ctx.logger.warn(`mcp-sse-client(${config.serverName}): tool refresh failed: ${messageOf(error)}`)
|
|
117
|
+
})
|
|
70
118
|
})
|
|
71
|
-
})
|
|
72
119
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
120
|
+
try {
|
|
121
|
+
await client.connect(transport)
|
|
122
|
+
await synchronizeTools(client, generation)
|
|
123
|
+
failedAttempts = 0
|
|
124
|
+
ctx.logger.info(`mcp-sse-client(${config.serverName}): connected and synchronized tools`)
|
|
125
|
+
} catch (error) {
|
|
126
|
+
onDisconnect(error)
|
|
127
|
+
if (startup && config.failOnStartupError) {
|
|
128
|
+
throw new Error(`mcp-sse-client(${config.serverName}): initial connection failed: ${messageOf(error)}`, { cause: error })
|
|
129
|
+
}
|
|
130
|
+
}
|
|
81
131
|
}
|
|
132
|
+
|
|
133
|
+
ctx.effect(() => () => {
|
|
134
|
+
disposed = true
|
|
135
|
+
if (reconnectTimer !== undefined) {
|
|
136
|
+
clearTimeout(reconnectTimer)
|
|
137
|
+
reconnectTimer = undefined
|
|
138
|
+
}
|
|
139
|
+
clearTools()
|
|
140
|
+
if (currentGeneration && !currentGeneration.closed) {
|
|
141
|
+
currentGeneration.closed = true
|
|
142
|
+
void currentGeneration.client.close().catch(() => {})
|
|
143
|
+
}
|
|
144
|
+
}, 'mcp-sse-client.connection')
|
|
145
|
+
|
|
146
|
+
await connectGeneration(true)
|
|
82
147
|
}
|
|
83
148
|
|
|
84
149
|
function createTool(client, publicName, tool, timeout) {
|
package/src/store.js
CHANGED
|
@@ -39,12 +39,13 @@ function validateId(id) {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function parsePatch(value) {
|
|
42
|
-
if (!Array.isArray(value)
|
|
43
|
-
|
|
42
|
+
if (!Array.isArray(value)) throw new Error('Expected patch array.')
|
|
43
|
+
const insertOp = value.find(row => row && typeof row === 'object' && Array.isArray(row.insert))
|
|
44
|
+
if (!insertOp) throw new Error('Expected one managed insert operation.')
|
|
44
45
|
const ids = new Set()
|
|
45
46
|
const managed = []
|
|
46
47
|
const preserved = []
|
|
47
|
-
for (const row of
|
|
48
|
+
for (const row of insertOp.insert) {
|
|
48
49
|
if (row?.id === 'extension-manager' && row.name === managerModule) {
|
|
49
50
|
if (ids.has(row.id)) throw new Error('Duplicate extension id.')
|
|
50
51
|
ids.add(row.id)
|
|
@@ -69,7 +70,8 @@ function parsePatch(value) {
|
|
|
69
70
|
} else throw new Error('Unknown managed plugin module.')
|
|
70
71
|
managed.push(row)
|
|
71
72
|
}
|
|
72
|
-
|
|
73
|
+
const unmanagedRows = value.filter(row => row !== insertOp)
|
|
74
|
+
return { managed, preserved, unmanagedRows }
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
/** Validate the patch and return only extension rows owned by the manager. */
|
|
@@ -84,7 +86,8 @@ function markedRows(content) {
|
|
|
84
86
|
if (start < 0 || end < start || content.indexOf(blockStart, start + blockStart.length) >= 0
|
|
85
87
|
|| content.indexOf(blockEnd, end + blockEnd.length) >= 0) throw new Error('Invalid dsh-skill-mcp managed block.')
|
|
86
88
|
const bodyStart = start + blockStart.length
|
|
87
|
-
|
|
89
|
+
const parsed = parsePatch(parse(content.slice(bodyStart, end)))
|
|
90
|
+
return { start, end: end + blockEnd.length, rows: parsed.managed, unmanagedRows: parsed.unmanagedRows }
|
|
88
91
|
}
|
|
89
92
|
|
|
90
93
|
function legacyPatch(content) {
|
|
@@ -99,7 +102,10 @@ function renderBlock(rows) {
|
|
|
99
102
|
function writeManagedRows(content, rows) {
|
|
100
103
|
const marked = markedRows(content)
|
|
101
104
|
const block = renderBlock(rows)
|
|
102
|
-
if (marked)
|
|
105
|
+
if (marked) {
|
|
106
|
+
const unmanaged = marked.unmanagedRows?.length ? `\n${stringify(marked.unmanagedRows).trimEnd()}\n` : ''
|
|
107
|
+
return `${content.slice(0, marked.start)}${block}${unmanaged}${content.slice(marked.end)}`
|
|
108
|
+
}
|
|
103
109
|
const legacy = legacyPatch(content)
|
|
104
110
|
if (legacy) return `${JSON.stringify([{ insert: [...legacy.preserved, ...rows] }], null, 2)}\n`
|
|
105
111
|
if (/^[ \t]*\[\][ \t]*$/m.test(content)) return content.replace(/^[ \t]*\[\][ \t]*$/m, block)
|
|
@@ -121,10 +127,28 @@ export class ExtensionStore {
|
|
|
121
127
|
/** Migrate managed MCP rows so an unavailable server cannot reject profile startup. */
|
|
122
128
|
async ensureNonFatalMcpStartup() {
|
|
123
129
|
const rows = await this.read()
|
|
124
|
-
|
|
130
|
+
let needsUpdate = false
|
|
131
|
+
for (const row of rows) {
|
|
132
|
+
if (row.config?.serverName) {
|
|
133
|
+
if (row.config.failOnStartupError !== false || !row.config.reconnect || row.config.reconnect.maxAttempts !== Number.MAX_SAFE_INTEGER) {
|
|
134
|
+
needsUpdate = true
|
|
135
|
+
break
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
if (!needsUpdate) return
|
|
125
140
|
await this.update(current => {
|
|
126
141
|
for (const row of current) {
|
|
127
|
-
if (row.config
|
|
142
|
+
if (row.config?.serverName) {
|
|
143
|
+
row.config.failOnStartupError = false
|
|
144
|
+
const maxDelay = row.config.reconnect?.maxDelayMs ?? 7000
|
|
145
|
+
row.config.reconnect = {
|
|
146
|
+
enabled: true,
|
|
147
|
+
initialDelayMs: 500,
|
|
148
|
+
maxDelayMs: Math.min(60_000, Math.max(3_000, maxDelay)),
|
|
149
|
+
maxAttempts: Number.MAX_SAFE_INTEGER,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
128
152
|
}
|
|
129
153
|
})
|
|
130
154
|
}
|
|
@@ -195,11 +219,23 @@ export class ExtensionStore {
|
|
|
195
219
|
normalizeMcp(id, input) {
|
|
196
220
|
// Authentication values belong in the host credential setup, not tool arguments or logs.
|
|
197
221
|
if (input.env || input.headers) throw new Error('This version does not accept environment values or authentication headers.')
|
|
222
|
+
const reconnectIntervalSec = Math.min(60, Math.max(3, Math.round(Number(input.reconnectInterval ?? 7)) || 7))
|
|
223
|
+
const reconnect = {
|
|
224
|
+
enabled: true,
|
|
225
|
+
initialDelayMs: 500,
|
|
226
|
+
maxDelayMs: reconnectIntervalSec * 1000,
|
|
227
|
+
maxAttempts: Number.MAX_SAFE_INTEGER,
|
|
228
|
+
}
|
|
198
229
|
const normalized = input.transport === 'stdio' ? {
|
|
199
230
|
...input,
|
|
200
231
|
command: typeof input.command === 'string' ? normalizeWindowsPath(input.command) : input.command,
|
|
201
232
|
cwd: typeof input.cwd === 'string' && input.cwd ? normalizeWindowsPath(input.cwd) : input.cwd,
|
|
202
|
-
|
|
233
|
+
reconnect,
|
|
234
|
+
} : {
|
|
235
|
+
...input,
|
|
236
|
+
reconnect,
|
|
237
|
+
}
|
|
238
|
+
delete normalized.reconnectInterval
|
|
203
239
|
const config = input.transport === 'sse'
|
|
204
240
|
? SseMcpConfig({ ...normalized, serverName: id, failOnStartupError: false })
|
|
205
241
|
: McpConfig({ ...normalized, serverName: id, failOnStartupError: false })
|
package/src/web.js
CHANGED
|
@@ -55,8 +55,18 @@ export async function describeExtensions(store, ctx, options = {}) {
|
|
|
55
55
|
: row.config.customSkillDirs[0],
|
|
56
56
|
transport: row.config.transport ?? '',
|
|
57
57
|
configuration: row.config.serverName ? (row.config.transport === 'stdio'
|
|
58
|
-
? {
|
|
59
|
-
|
|
58
|
+
? {
|
|
59
|
+
transport: row.config.transport,
|
|
60
|
+
command: row.config.command,
|
|
61
|
+
args: row.config.args ?? [],
|
|
62
|
+
cwd: row.config.cwd ?? '',
|
|
63
|
+
reconnectInterval: row.config.reconnect?.maxDelayMs ? Math.round(row.config.reconnect.maxDelayMs / 1000) : 7,
|
|
64
|
+
}
|
|
65
|
+
: {
|
|
66
|
+
transport: row.config.transport,
|
|
67
|
+
url: row.config.url,
|
|
68
|
+
reconnectInterval: row.config.reconnect?.maxDelayMs ? Math.round(row.config.reconnect.maxDelayMs / 1000) : 7,
|
|
69
|
+
}) : undefined,
|
|
60
70
|
tools: row.config.serverName
|
|
61
71
|
? toolNames.filter(name => name.startsWith(`mcp__${row.id}__`)).sort() : [],
|
|
62
72
|
})),
|