dsh-capability-panel 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.i18n.yaml +4 -4
- package/README.ja.md +1 -0
- package/README.ko.md +1 -0
- package/README.md +1 -0
- package/README.zh.md +1 -0
- package/lib/client.js +224 -38
- package/lib/client.js.map +1 -1
- package/lib/index.d.ts +29 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +447 -208
- package/lib/index.js.map +1 -1
- package/package.json +1 -1
package/README.i18n.yaml
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
# confirmed-consistent state. All languages carry equal authority; after editing
|
|
3
3
|
# any side, bring the others along and re-record with:
|
|
4
4
|
# git hash-object README.md README.zh.md README.ja.md README.ko.md
|
|
5
|
-
README.md:
|
|
6
|
-
README.zh.md:
|
|
7
|
-
README.ja.md:
|
|
8
|
-
README.ko.md:
|
|
5
|
+
README.md: b68e24710f0c4d0c91b9e527fe459804cd7e56ce
|
|
6
|
+
README.zh.md: a1f55f347b685f52a25b362157555a718ea31ea6
|
|
7
|
+
README.ja.md: ca9550d9d4b11266728387f800d811e721a68845
|
|
8
|
+
README.ko.md: 3cc6504d2dfe44547bfe5ee3bdf72120f662dd29
|
package/README.ja.md
CHANGED
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
- **再起動しても消えないセッション単位のスイッチ。** 現在の会話でスキル・ツール・MCP サーバーまるごとをオフに。次のプロンプト組み立てから適用され、dsh の再起動後もそのセッションに紐づいたまま復元されます。他のセッションや会話履歴には一切触れません。
|
|
37
37
|
- **プリセットのデフォルト。** 設定 → 能力パネル で、エージェントプリセットごとのデフォルト能力セットを保存。以後に作成・復元されるセッションがそれを継承します。プリセットのデフォルトはあくまで起点で、セッション内で上書きできます。
|
|
38
38
|
- **MCP をサーバーごとにグループ化。** 2 つのサーバーに 200 個のツールがあっても見通せます。サーバーごと 1 行に畳み、1 回の書き込みで全体を切り替え。
|
|
39
|
+
- **オフラインのサーバーも一覧に残る。** ホスト構成で宣言されているのに現在ツールを 1 つも登録していない MCP サーバー(起動していないローカルのオンデマンドサービスなど)も行が残ります——「登録ツールなし」と正直に表示し、保存済みのオフ項目を列挙し、今すぐ接続をやり直す「リロード」ボタン付き。セッション途中で登録されたツールにも保存済みのデフォルトが自動で適用されます。
|
|
39
40
|
- **ソースごとにグループ化。** スキルと MCP サーバーはラベル付きの区切り線の下にまとまります:プリセット同梱のエントリーは出自のプリセット名を表示し、それ以外は実際のディレクトリ(`~/.dsh/skills`、プロジェクト相対パス、長い場合は中間を省略)を表示——「このスキルはどこから来たのか」が一目でわかります。
|
|
40
41
|
- **ワンクリックでソースフォルダへ。** グループの区切り線にホバーするとフォルダアイコンが現れ、クリックするとそのソースディレクトリをシステムのファイルマネージャーで開きます(macOS、Windows、freedesktop Linux)。
|
|
41
42
|
- **ブロック回数。** オフにした後もモデルがその能力を呼び続けた場合、パネルがカウントします——モデルが記憶から行動しているシグナルです。
|
package/README.ko.md
CHANGED
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
- **재시작해도 유지되는 세션 단위 스위치.** 현재 대화에서 스킬·도구·MCP 서버 전체를 끕니다. 다음 프롬프트 조립부터 적용되며, dsh 재시작 후에도 해당 세션에 바인딩된 채 복원됩니다. 다른 세션이나 대화 히스토리는 건드리지 않습니다.
|
|
37
37
|
- **프리셋 기본값.** 설정 → capability 패널에서 에이전트 프리셋별 기본 capability 세트를 저장합니다. 이후 생성되거나 복원되는 세션이 이를 상속합니다. 프리셋 기본값은 시작점일 뿐, 세션에서 언제든 재정의할 수 있습니다.
|
|
38
38
|
- **서버별 MCP 그룹화.** 두 서버에 200개의 도구가 있어도 한눈에 볼 수 있습니다. 서버당 한 행으로 접고, 한 번의 쓰기로 전체를 전환합니다.
|
|
39
|
+
- **오프라인 서버도 목록에 남습니다.** 호스트 구성에 선언되어 있지만 현재 도구를 하나도 등록하지 않은 MCP 서버(실행 중이지 않은 로컬 온디맨드 서비스 등)도 행이 유지됩니다——「등록된 도구 없음」으로 정직하게 표시하고, 이미 꺼둔 항목을 나열하며, 지금 즉시 연결을 다시 시도하는 **리로드** 버튼이 붙습니다. 세션 도중에 등록된 도구에도 저장된 기본값이 자동으로 적용됩니다.
|
|
39
40
|
- **출처별 그룹화.** 스킬과 MCP 서버가 라벨이 붙은 구분선 아래로 모입니다: 프리셋에 포함된 항목은 출처 프리셋 이름을 표시하고, 나머지는 실제 디렉터리(`~/.dsh/skills`, 프로젝트 상대 경로, 길면 중간 생략)를 표시합니다——"이 스킬은 어디서 왔지?"에 한눈에 답할 수 있습니다.
|
|
40
41
|
- **원클릭으로 소스 폴더 열기.** 그룹 구분선에 호버하면 폴더 아이콘이 나타나고, 클릭하면 해당 소스 디렉터리가 시스템 파일 관리자에서 열립니다(macOS, Windows, freedesktop Linux).
|
|
41
42
|
- **차단 횟수.** 끈 후에도 모델이 해당 capability를 계속 호출하면 패널이 카운트합니다——모델이 기억에서 행동하고 있다는 신호입니다.
|
package/README.md
CHANGED
|
@@ -38,6 +38,7 @@ This plugin turns both into one panel at the right of the composer.
|
|
|
38
38
|
- **Per-session switches that survive restarts.** Turn a skill, tool, or whole MCP server off for the current conversation. The switch applies from the next prompt assembly, stays bound to that session across a dsh restart, and never touches another session or the conversation history.
|
|
39
39
|
- **Preset defaults.** Settings → Capability Panel stores the default capability set per agent preset; sessions created or resumed afterward inherit it. Same filter, same grouping, same switches as the session panel — a preset default is a starting point the session can still override.
|
|
40
40
|
- **MCP grouped by server.** Two hundred tools behind two servers stay scannable: collapse to one row per server, flip the whole server in one write.
|
|
41
|
+
- **Offline servers stay listed.** An MCP server declared in the host composition but currently registering no tools (a local on-demand service that isn't running) still gets a row — marked honestly as "no tools registered", showing the positions already stored off for it, with a **Reload** button that retries the connection now. Late-registered tools also pick up the session's stored defaults automatically.
|
|
41
42
|
- **Grouped by source.** Skills and MCP servers cluster under labeled divider rules: preset-bundled entries name the preset they came from; everything else shows its real directory (`~/.dsh/skills`, project-relative paths, middle-ellipsized when long) — "where did this skill come from" is answered at a glance.
|
|
42
43
|
- **One click to the source folder.** Hover a group divider and a folder icon appears; clicking it opens that source directory in the system file manager (macOS, Windows, and freedesktop Linux).
|
|
43
44
|
- **Blocked-attempt counts.** If the model still calls a capability after you turned it off, the panel counts it — the signal that the model is acting from memory and the switch needs a louder story.
|
package/README.zh.md
CHANGED
|
@@ -38,6 +38,7 @@
|
|
|
38
38
|
- **重启不丢的会话级开关。** 关掉当前会话里的一个技能、一个工具或一整个 MCP 服务器:从下一次提示组装生效,重启 dsh 后随该会话恢复,且绝不碰其他会话、绝不动对话历史。
|
|
39
39
|
- **Preset 默认。** 设置 → 能力面板,为每个 agent preset 存一份默认能力集合;之后新建或恢复的会话继承它。与会话面板同一套筛选、分组和开关——preset 默认只是起点,会话里仍然可以覆盖。
|
|
40
40
|
- **MCP 按服务器分组。** 两个服务器挂着两百个工具也能扫得过来:折叠成每服务器一行,一次开关整组。
|
|
41
|
+
- **离线服务器照常列出。** 宿主配置里声明了、但当前没注册任何工具的 MCP 服务器(比如没启动的本地按需服务)仍会显示一行——如实标注「无已注册工具」,列出已存的关闭项,并附一个「重载」按钮立即重试连接。会话中途才注册上来的工具也会自动继承已存的默认。
|
|
41
42
|
- **按来源分组。** 技能和 MCP 服务器在带标签的分隔线下聚类:preset 自带的条目标注它来自哪个 preset,其余显示真实目录(`~/.dsh/skills`、项目相对路径,过长时中间省略)——"这个技能是从哪来的"一眼即有答案。
|
|
42
43
|
- **一键打开源文件夹。** 悬停分组分隔线会出现文件夹图标,点击即在系统文件管理器中打开该来源目录(macOS、Windows 和 freedesktop Linux)。
|
|
43
44
|
- **拦截计数。** 模型在你关掉某项之后仍然尝试调用,面板会计数——这是"模型在凭记忆行动、开关需要更响亮的告知"的信号。
|
package/lib/client.js
CHANGED
|
@@ -92,7 +92,9 @@ function parseMcpServerEntry(value) {
|
|
|
92
92
|
enabled: value["enabled"],
|
|
93
93
|
tools,
|
|
94
94
|
...source === void 0 ? {} : { source },
|
|
95
|
-
...path === void 0 ? {} : { path }
|
|
95
|
+
...path === void 0 ? {} : { path },
|
|
96
|
+
...value["unavailable"] === true ? { unavailable: true } : {},
|
|
97
|
+
...value["reconnectable"] === true ? { reconnectable: true } : {}
|
|
96
98
|
};
|
|
97
99
|
}
|
|
98
100
|
function parseBlocked(value) {
|
|
@@ -172,6 +174,16 @@ function close() {
|
|
|
172
174
|
open: false
|
|
173
175
|
});
|
|
174
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Surface an action failure (e.g. a rejected reload) in the panel's existing
|
|
179
|
+
* error slot, without clobbering the payload or loading state.
|
|
180
|
+
*/
|
|
181
|
+
function reportActionError(message) {
|
|
182
|
+
store$1.set({
|
|
183
|
+
...store$1.getSnapshot(),
|
|
184
|
+
error: message
|
|
185
|
+
});
|
|
186
|
+
}
|
|
175
187
|
const ROUTE$1 = "/api/capability-panel";
|
|
176
188
|
/**
|
|
177
189
|
* Reads and writes have different ordering domains. A newer refresh supersedes
|
|
@@ -385,6 +397,13 @@ const zh = {
|
|
|
385
397
|
"action.insert": "把 /{name} 填入输入框",
|
|
386
398
|
"server.tools": "{count} 工具",
|
|
387
399
|
"server.tool.one": "1 个工具",
|
|
400
|
+
"server.unavailable": "无已注册工具",
|
|
401
|
+
"server.unavailableHint": "宿主配置里声明了这个服务器,但它当前没有注册任何工具。原因无法从面板判定(服务未启动、正在重连、或本就没有工具都可能)。若它是个本地按需服务,先把程序开起来。",
|
|
402
|
+
"action.reload": "重载 {name}",
|
|
403
|
+
"action.reload.label": "重载",
|
|
404
|
+
"action.reload.ing": "正在重载 {name}…",
|
|
405
|
+
"action.reloadHint": "重新加载该服务器的插件实例(等同一次热重载):会断开并重连。对 stdio 类型的服务,这会重启其子进程。",
|
|
406
|
+
"action.reload.failed": "重载 {name} 失败:{error}",
|
|
388
407
|
"status.loading": "读取中…",
|
|
389
408
|
"status.error": "读取失败:{error}(可尝试刷新页面;宿主改动需重启 dsh 后生效)",
|
|
390
409
|
"empty.match": "无匹配项",
|
|
@@ -406,6 +425,8 @@ const zh = {
|
|
|
406
425
|
"filter.aria": "筛选技能与工具",
|
|
407
426
|
"filter.clear": "清空筛选",
|
|
408
427
|
"filter.count": "匹配 {shown} / {total} 项",
|
|
428
|
+
"footer.feedback": "反馈问题",
|
|
429
|
+
"footer.feedbackHint": "在 GitHub 上打开能力面板的 issue 页",
|
|
409
430
|
"disclosure.expand": "展开 {subject} 的{detail}",
|
|
410
431
|
"disclosure.collapse": "收起 {subject} 的{detail}",
|
|
411
432
|
"disclosure.pinned": "{subject} 的{detail}(筛选时保持展开)",
|
|
@@ -445,6 +466,13 @@ const en = {
|
|
|
445
466
|
"action.insert": "Insert /{name} into the composer",
|
|
446
467
|
"server.tools": "{count} tools",
|
|
447
468
|
"server.tool.one": "1 tool",
|
|
469
|
+
"server.unavailable": "no tools registered",
|
|
470
|
+
"server.unavailableHint": "This host declares the server, but it currently registers no tools. The panel cannot tell why (service not started, mid-reconnect, or genuinely tool-less are all possible). If it is a local on-demand service, start its program first.",
|
|
471
|
+
"action.reload": "Reload {name}",
|
|
472
|
+
"action.reload.label": "Reload",
|
|
473
|
+
"action.reload.ing": "Reloading {name}…",
|
|
474
|
+
"action.reloadHint": "Reload this server’s plugin instance (equivalent to one hot reload): it disconnects and reconnects. For a stdio service this restarts its child process.",
|
|
475
|
+
"action.reload.failed": "Failed to reload {name}: {error}",
|
|
448
476
|
"status.loading": "Loading…",
|
|
449
477
|
"status.error": "Failed to load: {error} (try refreshing the page; host changes take effect after a dsh restart)",
|
|
450
478
|
"empty.match": "No matches",
|
|
@@ -466,6 +494,8 @@ const en = {
|
|
|
466
494
|
"filter.aria": "Filter skills and tools",
|
|
467
495
|
"filter.clear": "Clear filter",
|
|
468
496
|
"filter.count": "{shown} / {total} matched",
|
|
497
|
+
"footer.feedback": "Report an issue",
|
|
498
|
+
"footer.feedbackHint": "Open the capability panel’s issues on GitHub",
|
|
469
499
|
"disclosure.expand": "Expand {detail} for {subject}",
|
|
470
500
|
"disclosure.collapse": "Collapse {detail} for {subject}",
|
|
471
501
|
"disclosure.pinned": "{detail} for {subject} (kept open while filtering)",
|
|
@@ -574,8 +604,12 @@ const PANEL_CSS = [
|
|
|
574
604
|
".ci-preset-server-trigger:disabled{cursor:default}",
|
|
575
605
|
".ci-preset-group .ci-preset-tool-list{margin:0;padding-left:26px}",
|
|
576
606
|
".ci-preset-picker-label{display:grid;gap:6px;color:var(--dsw-alias-label-primary,#0f1115);font-weight:600}",
|
|
577
|
-
".ci-preset-picker{height:
|
|
578
|
-
".ci-preset-picker:
|
|
607
|
+
".ci-preset-picker-trigger{height:28px;display:flex;align-items:center;gap:4px;padding:0 4px 0 8px;border:none;border-radius:24px;background:transparent;color:var(--dsw-alias-label-secondary,#61666b);font-size:13px;font-weight:500;line-height:20px;cursor:pointer;outline:none;max-width:280px}",
|
|
608
|
+
".ci-preset-picker-trigger:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06))}",
|
|
609
|
+
".ci-preset-picker-trigger:focus-visible{box-shadow:0 0 0 2px var(--dsw-alias-border-l3,rgba(0,0,0,.16))}",
|
|
610
|
+
".ci-preset-picker-name{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}",
|
|
611
|
+
".ci-preset-picker-chevron{color:var(--dsw-alias-label-caption,#81858c);flex:none;display:inline-grid;place-items:center;transition:transform .12s}",
|
|
612
|
+
".ci-preset-picker-chevron-open{transform:rotate(180deg)}",
|
|
579
613
|
".ci-settings-subtitle{margin:0 0 6px;color:var(--dsw-alias-label-primary,#0f1115);font-size:14px;line-height:22px}",
|
|
580
614
|
".ci-preset-tool-list{list-style:none;margin:0;padding:0}",
|
|
581
615
|
".ci-preset-tool-row{display:flex;align-items:center;gap:16px}",
|
|
@@ -590,7 +624,19 @@ const PANEL_CSS = [
|
|
|
590
624
|
".ci-preset-kinds .ci-tabs{display:inline-flex}",
|
|
591
625
|
".ci-preset-kinds .ci-tab{flex:0 0 auto;padding:0 12px}",
|
|
592
626
|
".ci-source-header:hover .ci-folder-icon{opacity:1 !important}",
|
|
627
|
+
".ci-reconnect{display:inline-flex;align-items:center;gap:5px;border:1px solid var(--dsw-alias-border-l2,rgba(0,0,0,.14));background:var(--dsw-alias-bg-base,#fff);color:var(--dsw-alias-label-secondary,#61666b);border-radius:8px;padding:3px 9px;font-size:12px;line-height:1.4;cursor:pointer;font-family:inherit;flex:none}",
|
|
628
|
+
".ci-reconnect:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,rgba(38,49,72,.06));color:var(--dsw-alias-label-primary,#0f1115)}",
|
|
629
|
+
".ci-reconnect:disabled{opacity:.7;cursor:default}",
|
|
630
|
+
".ci-reconnect:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#4176e6);outline-offset:1px}",
|
|
631
|
+
".ci-reconnect-glyph{display:inline-grid;place-items:center;width:12px;height:12px}",
|
|
632
|
+
".ci-reconnect-busy .ci-reconnect-glyph{animation:ci-reconnect-spin .8s linear infinite}",
|
|
633
|
+
"@keyframes ci-reconnect-spin{to{transform:rotate(360deg)}}",
|
|
634
|
+
"@media (prefers-reduced-motion: reduce){.ci-reconnect-busy .ci-reconnect-glyph{animation:none}}",
|
|
593
635
|
".ci-source-divider{list-style:none}",
|
|
636
|
+
".ci-feedback-link{display:inline-flex;align-items:center;gap:4px;color:var(--dsw-alias-label-tertiary,#8a8f98);font-size:12px;line-height:18px;text-decoration:none;cursor:pointer}",
|
|
637
|
+
".ci-feedback-link:hover{color:var(--dsw-alias-label-primary,#0f1115);text-decoration:underline}",
|
|
638
|
+
".ci-feedback-link:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#4176e6);outline-offset:2px;border-radius:3px}",
|
|
639
|
+
".ci-feedback-icon{display:inline-grid;place-items:center;flex:none}",
|
|
594
640
|
"@media (prefers-reduced-motion: reduce){.ci-thumb,.ci-panel,.ci-collapse,.ci-chevron svg{transition:none !important}}"
|
|
595
641
|
].join("\n");
|
|
596
642
|
|
|
@@ -4632,10 +4678,12 @@ function parsePresetToolPayload(value) {
|
|
|
4632
4678
|
const mcp = [];
|
|
4633
4679
|
for (const rawServer of rawMcp) {
|
|
4634
4680
|
if (!isRecord(rawServer)) return null;
|
|
4635
|
-
const { server, tools: rawTools, enabled, source, path } = rawServer;
|
|
4681
|
+
const { server, tools: rawTools, enabled, source, path, unavailable, reconnectable } = rawServer;
|
|
4636
4682
|
if (typeof server !== "string" || typeof enabled !== "boolean" || !Array.isArray(rawTools)) return null;
|
|
4637
4683
|
if (source !== void 0 && typeof source !== "string") return null;
|
|
4638
4684
|
if (path !== void 0 && typeof path !== "string") return null;
|
|
4685
|
+
if (unavailable !== void 0 && typeof unavailable !== "boolean") return null;
|
|
4686
|
+
if (reconnectable !== void 0 && typeof reconnectable !== "boolean") return null;
|
|
4639
4687
|
const tools = parseTools(rawTools);
|
|
4640
4688
|
if (tools === null) return null;
|
|
4641
4689
|
mcp.push({
|
|
@@ -4643,7 +4691,9 @@ function parsePresetToolPayload(value) {
|
|
|
4643
4691
|
tools,
|
|
4644
4692
|
enabled,
|
|
4645
4693
|
...source === void 0 ? {} : { source },
|
|
4646
|
-
...path === void 0 ? {} : { path }
|
|
4694
|
+
...path === void 0 ? {} : { path },
|
|
4695
|
+
...unavailable === void 0 ? {} : { unavailable },
|
|
4696
|
+
...reconnectable === void 0 ? {} : { reconnectable }
|
|
4647
4697
|
});
|
|
4648
4698
|
}
|
|
4649
4699
|
presets.push({
|
|
@@ -4782,6 +4832,41 @@ function setPresetServer(presetId, server, enabled) {
|
|
|
4782
4832
|
enabled
|
|
4783
4833
|
});
|
|
4784
4834
|
}
|
|
4835
|
+
/**
|
|
4836
|
+
* Pull a declared-but-offline server's connection up, then reload the preset
|
|
4837
|
+
* list: registration lands asynchronously, so the payload refresh reflects
|
|
4838
|
+
* whatever the registry knows when it answers.
|
|
4839
|
+
*/
|
|
4840
|
+
function reconnectPresetServer(server) {
|
|
4841
|
+
const requestEpoch = begin();
|
|
4842
|
+
const run = async () => {
|
|
4843
|
+
try {
|
|
4844
|
+
const response = await fetch("/api/capability-panel/reconnect", {
|
|
4845
|
+
method: "POST",
|
|
4846
|
+
credentials: "same-origin",
|
|
4847
|
+
headers: { "content-type": "application/json" },
|
|
4848
|
+
body: JSON.stringify({ server })
|
|
4849
|
+
});
|
|
4850
|
+
if (!response.ok) {
|
|
4851
|
+
let detail = "";
|
|
4852
|
+
try {
|
|
4853
|
+
const body = await response.json();
|
|
4854
|
+
if (typeof body.error === "string") detail = `: ${body.error}`;
|
|
4855
|
+
} catch {}
|
|
4856
|
+
throw new Error(`HTTP ${response.status}${detail}`);
|
|
4857
|
+
}
|
|
4858
|
+
finish(requestEpoch, {
|
|
4859
|
+
payload: await requestPayload(),
|
|
4860
|
+
error: null
|
|
4861
|
+
});
|
|
4862
|
+
} catch (error) {
|
|
4863
|
+
finish(requestEpoch, { error: error instanceof Error ? error.message : String(error) });
|
|
4864
|
+
}
|
|
4865
|
+
};
|
|
4866
|
+
const result = queue.then(run);
|
|
4867
|
+
queue = result;
|
|
4868
|
+
return result;
|
|
4869
|
+
}
|
|
4785
4870
|
function resetPresetTools() {
|
|
4786
4871
|
epoch += 1;
|
|
4787
4872
|
requests = 0;
|
|
@@ -4830,6 +4915,8 @@ function PresetToolSection(props) {
|
|
|
4830
4915
|
const [query, setQuery] = react.useState("");
|
|
4831
4916
|
const [expanded, setExpanded] = react.useState({});
|
|
4832
4917
|
const [kind, setKind] = react.useState("all");
|
|
4918
|
+
const [pickerOpen, setPickerOpen] = react.useState(false);
|
|
4919
|
+
const [reconnecting, setReconnecting] = react.useState(null);
|
|
4833
4920
|
const selected = state.payload?.presets.find((preset) => preset.id === state.selectedId);
|
|
4834
4921
|
const filtering = query.trim() !== "";
|
|
4835
4922
|
const view = selected === void 0 ? null : filterPreset(selected, query);
|
|
@@ -4927,16 +5014,7 @@ function PresetToolSection(props) {
|
|
|
4927
5014
|
opacity: 0,
|
|
4928
5015
|
transition: "opacity 0.15s"
|
|
4929
5016
|
}
|
|
4930
|
-
}, react.createElement(
|
|
4931
|
-
width: "12",
|
|
4932
|
-
height: "12",
|
|
4933
|
-
viewBox: "0 0 24 24",
|
|
4934
|
-
fill: "none",
|
|
4935
|
-
stroke: "currentColor",
|
|
4936
|
-
"stroke-width": "2",
|
|
4937
|
-
"stroke-linecap": "round",
|
|
4938
|
-
"stroke-linejoin": "round"
|
|
4939
|
-
}, react.createElement("path", { d: "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" }))) : null, `${label} (${items.length})`), rule(true)));
|
|
5017
|
+
}, react.createElement(__deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 12 })) : null, `${label} (${items.length})`), rule(true)));
|
|
4940
5018
|
};
|
|
4941
5019
|
const toggle$1 = (tool, presetId) => switchFor(tool.enabled, tool.reserved === true, tool.reserved === true ? t("preset.reserved", { name: tool.label }) : t(tool.enabled ? "action.disable" : "action.enable", { name: tool.label }), (checked) => {
|
|
4942
5020
|
setPresetTool(presetId, tool.name, checked);
|
|
@@ -5050,9 +5128,30 @@ function PresetToolSection(props) {
|
|
|
5050
5128
|
}, react.createElement("span", {
|
|
5051
5129
|
className: "ci-chevron",
|
|
5052
5130
|
"aria-hidden": true
|
|
5053
|
-
}, chevronIcon), react.createElement("span", { className: "ci-preset-tool-copy" }, react.createElement("span", { className: "ci-preset-tool-name" }, server.server), react.createElement("span", { className: "ci-preset-tool-description" }, server.tools.length === 1 ? t("server.tool.one") : t("server.tools", { count: server.tools.length })))),
|
|
5131
|
+
}, chevronIcon), react.createElement("span", { className: "ci-preset-tool-copy" }, react.createElement("span", { className: "ci-preset-tool-name" }, server.server), server.unavailable === true ? null : react.createElement("span", { className: "ci-preset-tool-description" }, server.tools.length === 1 ? t("server.tool.one") : t("server.tools", { count: server.tools.length })))), server.unavailable === true ? react.createElement("span", {
|
|
5132
|
+
className: "ci-preset-badge",
|
|
5133
|
+
title: t("server.unavailableHint")
|
|
5134
|
+
}, t("server.unavailable")) : switchFor(server.enabled, false, t(server.enabled ? "action.disable" : "action.enable", { name: server.server }), (checked) => {
|
|
5054
5135
|
setPresetServer(selected.id, server.server, checked);
|
|
5055
|
-
})
|
|
5136
|
+
}), server.unavailable === true && server.reconnectable === true ? react.createElement("button", {
|
|
5137
|
+
type: "button",
|
|
5138
|
+
className: `ci-reconnect${reconnecting === server.server ? " ci-reconnect-busy" : ""}`,
|
|
5139
|
+
disabled: reconnecting !== null,
|
|
5140
|
+
title: reconnecting === server.server ? t("action.reload.ing", { name: server.server }) : `${t("action.reload", { name: server.server })}\n${t("action.reloadHint")}`,
|
|
5141
|
+
"aria-label": t("action.reload", { name: server.server }),
|
|
5142
|
+
onClick: () => {
|
|
5143
|
+
if (reconnecting !== null) return;
|
|
5144
|
+
setReconnecting(server.server);
|
|
5145
|
+
reconnectPresetServer(server.server).finally(() => {
|
|
5146
|
+
setTimeout(() => {
|
|
5147
|
+
setReconnecting(null);
|
|
5148
|
+
}, 2500);
|
|
5149
|
+
});
|
|
5150
|
+
}
|
|
5151
|
+
}, react.createElement("span", {
|
|
5152
|
+
className: "ci-reconnect-glyph",
|
|
5153
|
+
"aria-hidden": true
|
|
5154
|
+
}, react.createElement(__deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline14, { size: 12 })), t("action.reload.label")) : null), react.createElement(CollapsiblePanel, { className: "ci-collapse" }, react.createElement("ul", { className: "ci-preset-tool-list" }, ...server.tools.map((tool) => toolRow(tool, selected.id, true))))));
|
|
5056
5155
|
};
|
|
5057
5156
|
group("mcp", t("group.mcp", {
|
|
5058
5157
|
shown: view.mcp.length,
|
|
@@ -5074,16 +5173,34 @@ function PresetToolSection(props) {
|
|
|
5074
5173
|
}, t("status.loading")) : null, state.payload === null ? null : react.createElement(react.Fragment, null, writable ? null : react.createElement("p", {
|
|
5075
5174
|
role: "note",
|
|
5076
5175
|
className: "ci-settings-note"
|
|
5077
|
-
}, t("preset.readonly")), react.createElement("div", { className: "ci-preset-toolbar" }, react.createElement("
|
|
5078
|
-
|
|
5079
|
-
|
|
5080
|
-
|
|
5081
|
-
|
|
5176
|
+
}, t("preset.readonly")), react.createElement("div", { className: "ci-preset-toolbar" }, react.createElement("span", { className: "ci-preset-picker-label" }, react.createElement("span", null, t("preset.choose")), react.createElement(__deepseek_ai_dsh_client_ui_primitives.Menu, {
|
|
5177
|
+
open: pickerOpen,
|
|
5178
|
+
anchor: react.createElement("button", {
|
|
5179
|
+
type: "button",
|
|
5180
|
+
className: "ci-preset-picker-trigger",
|
|
5181
|
+
"aria-haspopup": "menu",
|
|
5182
|
+
"aria-expanded": pickerOpen,
|
|
5183
|
+
"aria-label": t("preset.choose"),
|
|
5184
|
+
onClick: () => {
|
|
5185
|
+
setPickerOpen((open) => !open);
|
|
5186
|
+
}
|
|
5187
|
+
}, react.createElement("span", { className: "ci-preset-picker-name" }, selected?.name ?? ""), react.createElement("span", {
|
|
5188
|
+
className: `ci-preset-picker-chevron${pickerOpen ? " ci-preset-picker-chevron-open" : ""}`,
|
|
5189
|
+
"aria-hidden": true
|
|
5190
|
+
}, react.createElement(__deepseek_ai_dsh_client_ui_primitives.IconChevronDownOutline14, { size: 14 }))),
|
|
5191
|
+
items: state.payload.presets.map((preset) => ({
|
|
5192
|
+
id: preset.id,
|
|
5193
|
+
label: preset.name
|
|
5194
|
+
})),
|
|
5195
|
+
...state.selectedId === null ? {} : { selectedId: state.selectedId },
|
|
5196
|
+
onSelect: (id) => {
|
|
5197
|
+
selectPreset(id);
|
|
5198
|
+
setPickerOpen(false);
|
|
5199
|
+
},
|
|
5200
|
+
onClose: () => {
|
|
5201
|
+
setPickerOpen(false);
|
|
5082
5202
|
}
|
|
5083
|
-
},
|
|
5084
|
-
key: preset.id,
|
|
5085
|
-
value: preset.id
|
|
5086
|
-
}, preset.name)))), react.createElement(Input, {
|
|
5203
|
+
})), react.createElement(Input, {
|
|
5087
5204
|
className: "ci-filter ci-preset-filter",
|
|
5088
5205
|
value: query,
|
|
5089
5206
|
placeholder: t("filter.placeholder"),
|
|
@@ -10792,6 +10909,8 @@ const PopoverPopup = /* @__PURE__ */ react.forwardRef(function PopoverPopup$1(co
|
|
|
10792
10909
|
//#endregion
|
|
10793
10910
|
//#region src/client/index.ts
|
|
10794
10911
|
const ROUTE = "/api/capability-panel";
|
|
10912
|
+
/** The plugin's issue tracker, surfaced as the panel foot's feedback link. */
|
|
10913
|
+
const FEEDBACK_URL = "https://github.com/pure-craft/dsh-capability-panel/issues";
|
|
10795
10914
|
/**
|
|
10796
10915
|
* Order by how much the reader needs to act on it: what fell out of context
|
|
10797
10916
|
* first, then what is in it, then the rest.
|
|
@@ -10833,6 +10952,17 @@ function apply(ctx) {
|
|
|
10833
10952
|
/** Paper plane: the row action lands the command in the composer,
|
|
10834
10953
|
* ready for the user's own Enter. */
|
|
10835
10954
|
const insertIcon = (size$3) => h(__deepseek_ai_dsh_client_ui_primitives.IconSendOutline14, { size: size$3 });
|
|
10955
|
+
/** Circular arrows: pull a declared-but-offline server's connection up now. */
|
|
10956
|
+
const reconnectIcon = (size$3) => h(__deepseek_ai_dsh_client_ui_primitives.IconRefreshOutline14, { size: size$3 });
|
|
10957
|
+
/** The GitHub mark. Not in the host icon set (brand logos are not shipped
|
|
10958
|
+
* there), so this one path is inlined for the feedback link's recognizability. */
|
|
10959
|
+
const githubIcon = (size$3) => h("svg", {
|
|
10960
|
+
width: size$3,
|
|
10961
|
+
height: size$3,
|
|
10962
|
+
viewBox: "0 0 1024 1024",
|
|
10963
|
+
fill: "currentColor",
|
|
10964
|
+
"aria-hidden": true
|
|
10965
|
+
}, h("path", { d: "M511.6 76.3C264.3 76.2 64 276.4 64 523.5 64 718.9 189.3 885 363.8 946c23.5 5.9 19.9-10.8 19.9-22.2v-77.5c-135.7 15.9-141.2-73.9-150.3-88.9C215 726 171.5 718 184.5 703c30.9-15.9 62.4 4 98.9 57.9 26.4 39.1 77.9 32.5 104 26 5.7-23.5 17.9-44.5 34.7-60.8-140.6-25.2-199.2-111-199.2-213 0-49.5 16.3-95 48.3-131.7-20.4-60.5 1.9-112.3 4.9-120 58.1-5.2 118.5 41.6 123.2 45.3 33-8.9 70.7-13.6 112.9-13.6 42.4 0 80.2 4.9 113.5 13.9 11.3-8.6 67.3-48.8 121.3-43.9 2.9 7.7 24.7 58.3 5.5 118 32.4 36.8 48.9 82.7 48.9 132.3 0 102.2-59 188.1-200 212.9 23.5 23.2 38.1 55.4 38.1 91v112.5c0.8 9 0 17.9 15 17.9 177.1-59.7 304.6-227 304.6-424.1 0-247.2-200.4-447.3-447.5-447.3z" }));
|
|
10836
10966
|
ctx.slots.inject("settings.section", () => ctx.slots.register({
|
|
10837
10967
|
name: "settings.section",
|
|
10838
10968
|
id: "capability-panel",
|
|
@@ -10853,6 +10983,7 @@ function apply(ctx) {
|
|
|
10853
10983
|
const [expanded, setExpanded] = react$1.useState({});
|
|
10854
10984
|
const [query, setQuery] = react$1.useState("");
|
|
10855
10985
|
const [tab, setTab] = react$1.useState("skills");
|
|
10986
|
+
const [reconnecting, setReconnecting] = react$1.useState(null);
|
|
10856
10987
|
const sessionId = props.sessionId ?? null;
|
|
10857
10988
|
react$1.useEffect(() => {
|
|
10858
10989
|
if (snap.open) refresh(sessionId);
|
|
@@ -10890,6 +11021,41 @@ function apply(ctx) {
|
|
|
10890
11021
|
});
|
|
10891
11022
|
};
|
|
10892
11023
|
/**
|
|
11024
|
+
* Pull a declared-but-offline server's connection up, then refresh:
|
|
11025
|
+
* registration lands asynchronously, and the registry's tools/change
|
|
11026
|
+
* broadcast is what re-applies this session's stored positions.
|
|
11027
|
+
*/
|
|
11028
|
+
const reconnectServer = (server) => {
|
|
11029
|
+
if (reconnecting !== null) return;
|
|
11030
|
+
setReconnecting(server);
|
|
11031
|
+
fetch(`${ROUTE}/reconnect`, {
|
|
11032
|
+
method: "POST",
|
|
11033
|
+
credentials: "same-origin",
|
|
11034
|
+
headers: { "content-type": "application/json" },
|
|
11035
|
+
body: JSON.stringify({ server })
|
|
11036
|
+
}).then(async (response) => {
|
|
11037
|
+
if (!response.ok) {
|
|
11038
|
+
const detail = await response.text();
|
|
11039
|
+
console.warn(`[capability-panel] reconnect failed (${response.status}): ${detail}`);
|
|
11040
|
+
reportActionError(t("action.reload.failed", {
|
|
11041
|
+
name: server,
|
|
11042
|
+
error: `HTTP ${response.status}`
|
|
11043
|
+
}));
|
|
11044
|
+
}
|
|
11045
|
+
await refresh(sessionId);
|
|
11046
|
+
}).catch((error) => {
|
|
11047
|
+
console.warn("[capability-panel] reconnect request failed", error);
|
|
11048
|
+
reportActionError(t("action.reload.failed", {
|
|
11049
|
+
name: server,
|
|
11050
|
+
error: error instanceof Error ? error.message : String(error)
|
|
11051
|
+
}));
|
|
11052
|
+
}).finally(() => {
|
|
11053
|
+
setTimeout(() => {
|
|
11054
|
+
setReconnecting(null);
|
|
11055
|
+
}, 2500);
|
|
11056
|
+
});
|
|
11057
|
+
};
|
|
11058
|
+
/**
|
|
10893
11059
|
* Middle ellipsis for long path labels: both ends carry the meaning
|
|
10894
11060
|
* (`~/…` context, the tail directory), the middle is the expendable
|
|
10895
11061
|
* part. The full path stays in the hover tooltip.
|
|
@@ -10953,16 +11119,7 @@ function apply(ctx) {
|
|
|
10953
11119
|
opacity: 0,
|
|
10954
11120
|
transition: "opacity 0.15s"
|
|
10955
11121
|
}
|
|
10956
|
-
}, h(
|
|
10957
|
-
width: "12",
|
|
10958
|
-
height: "12",
|
|
10959
|
-
viewBox: "0 0 24 24",
|
|
10960
|
-
fill: "none",
|
|
10961
|
-
stroke: "currentColor",
|
|
10962
|
-
"stroke-width": "2",
|
|
10963
|
-
"stroke-linecap": "round",
|
|
10964
|
-
"stroke-linejoin": "round"
|
|
10965
|
-
}, h("path", { d: "M22 19a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h5l2 3h9a2 2 0 0 1 2 2z" }))) : null, `${label} (${count})`), rule(true));
|
|
11122
|
+
}, h(__deepseek_ai_dsh_client_ui_primitives.IconFolderClose16, { size: 12 })) : null, `${label} (${count})`), rule(true));
|
|
10966
11123
|
};
|
|
10967
11124
|
/** Group items by source, preserving order within each group. */
|
|
10968
11125
|
const groupBySource = (items, getSource) => {
|
|
@@ -11188,7 +11345,23 @@ function apply(ctx) {
|
|
|
11188
11345
|
}, h("span", {
|
|
11189
11346
|
className: "ci-chevron",
|
|
11190
11347
|
"aria-hidden": true
|
|
11191
|
-
}, chevronIcon$1)), nameText(server.server),
|
|
11348
|
+
}, chevronIcon$1)), nameText(server.server), server.unavailable === true ? h("span", {
|
|
11349
|
+
className: "ci-preset-badge",
|
|
11350
|
+
title: t("server.unavailableHint"),
|
|
11351
|
+
style: { flex: "none" }
|
|
11352
|
+
}, t("server.unavailable")) : metaText(server.tools.length === 1 ? t("server.tool.one") : t("server.tools", { count: server.tools.length })), blockedChip(serverBlocked), server.unavailable === true && server.reconnectable === true ? h("button", {
|
|
11353
|
+
type: "button",
|
|
11354
|
+
className: `ci-reconnect${reconnecting === server.server ? " ci-reconnect-busy" : ""}`,
|
|
11355
|
+
disabled: reconnecting !== null,
|
|
11356
|
+
"aria-label": t("action.reload", { name: server.server }),
|
|
11357
|
+
title: reconnecting === server.server ? t("action.reload.ing", { name: server.server }) : `${t("action.reload", { name: server.server })}\n${t("action.reloadHint")}`,
|
|
11358
|
+
onClick: () => {
|
|
11359
|
+
reconnectServer(server.server);
|
|
11360
|
+
}
|
|
11361
|
+
}, h("span", {
|
|
11362
|
+
className: "ci-reconnect-glyph",
|
|
11363
|
+
"aria-hidden": true
|
|
11364
|
+
}, reconnectIcon(12)), t("action.reload.label")) : null, server.unavailable === true ? null : switchControl("mcp-server", server.server, server.enabled)), h(CollapsiblePanel, { className: "ci-collapse" }, h("div", { style: {
|
|
11192
11365
|
marginTop: "2px",
|
|
11193
11366
|
marginLeft: "4px",
|
|
11194
11367
|
paddingLeft: "8px",
|
|
@@ -11394,7 +11567,20 @@ function apply(ctx) {
|
|
|
11394
11567
|
}, t("filter.count", {
|
|
11395
11568
|
shown: view?.total ?? 0,
|
|
11396
11569
|
total: totalAll
|
|
11397
|
-
})) : null), ...notices, ...body
|
|
11570
|
+
})) : null), ...notices, ...body, h("div", { style: {
|
|
11571
|
+
display: "flex",
|
|
11572
|
+
justifyContent: "flex-end",
|
|
11573
|
+
marginTop: "10px"
|
|
11574
|
+
} }, h("a", {
|
|
11575
|
+
href: FEEDBACK_URL,
|
|
11576
|
+
target: "_blank",
|
|
11577
|
+
rel: "noopener noreferrer",
|
|
11578
|
+
className: "ci-feedback-link",
|
|
11579
|
+
title: t("footer.feedbackHint")
|
|
11580
|
+
}, h("span", {
|
|
11581
|
+
className: "ci-feedback-icon",
|
|
11582
|
+
"aria-hidden": true
|
|
11583
|
+
}, githubIcon(12)), t("footer.feedback")))))));
|
|
11398
11584
|
}));
|
|
11399
11585
|
}
|
|
11400
11586
|
const inject = ["slots", "locale"];
|